Compare commits
167 Commits
Author | SHA1 | Date | |
---|---|---|---|
ece9cd8667 | |||
bf0c946c52 | |||
273d229709 | |||
c8dfc3b459 | |||
cf6b3e2603 | |||
c81c89197b | |||
6e79b0c50f | |||
d9e52c85c3 | |||
6c04aa683e | |||
fd08fa3883 | |||
3e286c08b4 | |||
644dcc692f | |||
009346cd30 | |||
1a33f744d6 | |||
0478431c9c | |||
d3a06e7639 | |||
35d7486869 | |||
a8addf946b | |||
a245288359 | |||
aee5debdd2 | |||
322bddcc4f | |||
894e0ff724 | |||
1d3340a547 | |||
83e50e1ad1 | |||
fa0e53289c | |||
811625c051 | |||
869d2e0386 | |||
239c6ec7f4 | |||
956ed61b14 | |||
c4b26cac1c | |||
2ae1e561d8 | |||
e5c956c6e5 | |||
f50d7445c1 | |||
95c9be4d1c | |||
dca0330a7c | |||
7185acbbad | |||
c4385b2336 | |||
841fb7eccd | |||
016b4f80ba | |||
5849e68d20 | |||
3ab3d7a0ca | |||
d9090eb70c | |||
10a45cc79c | |||
3f8980e236 | |||
732032b1b5 | |||
702ac6cb82 | |||
0d67701735 | |||
c11a1b78c4 | |||
6d67598a40 | |||
e1dcbbf19c | |||
31eca20cca | |||
0bb455d433 | |||
831e4bcbcc | |||
df7f03d81a | |||
64ee41aee8 | |||
d3205336ed | |||
7b14081c63 | |||
e1173d0619 | |||
f0a5a40edd | |||
1bc82cdd7d | |||
a880a8c10b | |||
6a5fe01860 | |||
5cf6d308f2 | |||
ad97cd0633 | |||
5043e9ed03 | |||
15c6540e16 | |||
784ed9ed4e | |||
3a0555e6e9 | |||
e71f30736d | |||
503e665c5b | |||
579d604ac6 | |||
1003e7a582 | |||
a18f76f864 | |||
7e201dd2cf | |||
83abce0f52 | |||
e6d40cde76 | |||
71124d7b01 | |||
489b8968e0 | |||
a240606909 | |||
dad05d835b | |||
827973f642 | |||
53aa2c6804 | |||
102d63e2c6 | |||
caa2219a63 | |||
3a92bab9ba | |||
1eac644813 | |||
41c4447514 | |||
631e71bc74 | |||
86cfc66a40 | |||
52422d3398 | |||
cd3a801704 | |||
505818a712 | |||
92229151a5 | |||
334a510743 | |||
56ebb7b8fa | |||
48ff975ffd | |||
82ee4d05bb | |||
50f2c7e080 | |||
d130080df2 | |||
abc2f63aa6 | |||
ae2120675f | |||
0fadb40c6c | |||
ed4fd1002b | |||
62d40fb2c3 | |||
12fb794203 | |||
cde4f7b3ce | |||
425a9dd6bf | |||
c1fa642b4e | |||
08f2d5d6f1 | |||
b8f88b02d1 | |||
5c4892d153 | |||
3d2cea91ce | |||
22108527f3 | |||
725d2b848e | |||
409dccd0be | |||
ed6344c662 | |||
14849a82ea | |||
c3d8a07e83 | |||
43f42dd400 | |||
e44dd967d0 | |||
a67ac27921 | |||
05d9ea93d2 | |||
de8f04e22a | |||
4f0b2cb8f8 | |||
678a48ac3f | |||
c7594795f0 | |||
ea2cd7bf53 | |||
8df42a6cf0 | |||
784b192bab | |||
a0505f5704 | |||
61890b3da9 | |||
ddd5826f42 | |||
c31ec7a9e5 | |||
6f4f51aef3 | |||
e8ddf90363 | |||
4f7f80c12a | |||
f70c15da38 | |||
32ea3517fe | |||
ead5341b2e | |||
3e5e431d6f | |||
57057298a1 | |||
9ca28ead66 | |||
2a17e6024f | |||
bc68a5d3ec | |||
382cb80a87 | |||
f6add5b2ea | |||
0a00a0a1b4 | |||
9f1e0057c9 | |||
4dd639e651 | |||
f5215be8c6 | |||
a72718d4e8 | |||
8b2133977c | |||
f17f26bf34 | |||
86accb94f2 | |||
ccb102efd7 | |||
f71671e04a | |||
e4a69f42b0 | |||
af3e63f592 | |||
60adf73616 | |||
9c375d6608 | |||
db64b55510 | |||
06dbfbf64a | |||
58e9644ff1 | |||
2bfeedc7a9 | |||
ea700755b6 | |||
5ab577f18a | |||
2fae90c433 |
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 4
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@
|
|||||||
### Gradle ###
|
### Gradle ###
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
|
classes/
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
# Ignore Gradle GUI config
|
||||||
gradle-app.setting
|
gradle-app.setting
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
language: java
|
language: java
|
||||||
|
sudo: false # faster builds
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- pip install --user codecov
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- codecov
|
||||||
|
90
README.md
90
README.md
@ -1,9 +1,32 @@
|
|||||||
Jabit [](https://travis-ci.org/Dissem/Jabit)
|
Jabit
|
||||||
=====
|
=====
|
||||||
|
[](https://maven-badges.herokuapp.com/maven-central/ch.dissem.jabit/jabit-core)
|
||||||
|
[](http://www.javadoc.io/doc/ch.dissem.jabit/jabit-core)
|
||||||
|
[](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE)
|
||||||
|
[](https://kiwiirc.com/client/irc.freenode.net/#jabit)
|
||||||
|
|
||||||
A Java implementation for the Bitmessage protocol. To build, use command `gradle build` or `./gradlew build`.
|
A Java implementation for the Bitmessage protocol. To build, use command `./gradlew build`.
|
||||||
|
|
||||||
Please note that development is still heavily in progress, and I will break the database a lot until it's ready for prime time.
|
Please note that it still has its limitations, but the API should now be stable. Jabit uses Semantic Versioning, meaning as long as the major version doesn't change, nothing should break if you update.
|
||||||
|
|
||||||
|
Be aware though that this doesn't necessarily applies for SNAPSHOT builds and the development branch, notably when it comes to database updates. In other words, they may break your installation!_
|
||||||
|
|
||||||
|
#### Master
|
||||||
|
[](https://travis-ci.org/Dissem/Jabit)
|
||||||
|
[](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144281)
|
||||||
|
[](https://codecov.io/github/Dissem/Jabit?branch=master)
|
||||||
|
|
||||||
|
#### Develop
|
||||||
|
[](https://travis-ci.org/Dissem/Jabit?branch=develop)
|
||||||
|
[](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144279)
|
||||||
|
[](https://codecov.io/github/Dissem/Jabit?branch=develop)
|
||||||
|
|
||||||
|
Upgrading
|
||||||
|
---------
|
||||||
|
|
||||||
|
Please be aware that Version 2.0.0 has some breaking changes, most notably in the repository implementations -- please take special care when upgrading them. If you don't implement your own repositories, you should be able to quickly find and fix any compilation errors caused by the few other breaking changes.
|
||||||
|
|
||||||
|
There is also a new network handler which comes highly recommended. If you're having any network problems, please make sure you use `NioNetworkHandler` instead of the now deprecated `DefaultNetworkHandler`.
|
||||||
|
|
||||||
Security
|
Security
|
||||||
--------
|
--------
|
||||||
@ -15,7 +38,7 @@ Project Status
|
|||||||
|
|
||||||
Basically, everything needed for a working Bitmessage client is there:
|
Basically, everything needed for a working Bitmessage client is there:
|
||||||
* Creating new identities (private addresses)
|
* Creating new identities (private addresses)
|
||||||
* Adding contracts and subscriptions
|
* Adding contacts and subscriptions
|
||||||
* Receiving broadcasts
|
* Receiving broadcasts
|
||||||
* Receiving messages
|
* Receiving messages
|
||||||
* Sending messages and broadcasts
|
* Sending messages and broadcasts
|
||||||
@ -27,19 +50,23 @@ Basically, everything needed for a working Bitmessage client is there:
|
|||||||
Setup
|
Setup
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
It is recommended to define the version like this:
|
||||||
|
```Gradle
|
||||||
|
ext.jabitVersion = '2.0.0'
|
||||||
|
```
|
||||||
Add Jabit as Gradle dependency:
|
Add Jabit as Gradle dependency:
|
||||||
```Gradle
|
```Gradle
|
||||||
compile 'ch.dissem.jabit:jabit-core:1.0.0'
|
compile "ch.dissem.jabit:jabit-core:$jabitVersion"
|
||||||
```
|
```
|
||||||
Unless you want to implement your own, also add the following:
|
Unless you want to implement your own, also add the following:
|
||||||
```Gradle
|
```Gradle
|
||||||
compile 'ch.dissem.jabit:jabit-networking:1.0.0'
|
compile "ch.dissem.jabit:jabit-networking:$jabitVersion"
|
||||||
compile 'ch.dissem.jabit:jabit-repositories:1.0.0'
|
compile "ch.dissem.jabit:jabit-repositories:$jabitVersion"
|
||||||
compile 'ch.dissem.jabit:jabit-cryptography-bouncy:1.0.0'
|
compile "ch.dissem.jabit:jabit-cryptography-bouncy:$jabitVersion"
|
||||||
```
|
```
|
||||||
And if you want to import from or export to the Wallet Import Format (used by PyBitmessage) you might also want to add:
|
And if you want to import from or export to the Wallet Import Format (used by PyBitmessage) you might also want to add:
|
||||||
```Gradle
|
```Gradle
|
||||||
compile 'ch.dissem.jabit:jabit-wif:1.0.0'
|
compile "ch.dissem.jabit:jabit-wif:$jabitVersion"
|
||||||
```
|
```
|
||||||
|
|
||||||
For Android clients use `jabit-cryptography-spongy` instead of `jabit-cryptography-bouncy`.
|
For Android clients use `jabit-cryptography-spongy` instead of `jabit-cryptography-bouncy`.
|
||||||
@ -54,20 +81,26 @@ BitmessageContext ctx = new BitmessageContext.Builder()
|
|||||||
.addressRepo(new JdbcAddressRepository(jdbcConfig))
|
.addressRepo(new JdbcAddressRepository(jdbcConfig))
|
||||||
.inventory(new JdbcInventory(jdbcConfig))
|
.inventory(new JdbcInventory(jdbcConfig))
|
||||||
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||||
.nodeRegistry(new MemoryNodeRegistry())
|
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
|
||||||
.networkHandler(new NetworkNode())
|
.nodeRegistry(new JdbcNodeRegistry(jdbcConfig))
|
||||||
|
.networkHandler(new NioNetworkHandler())
|
||||||
.cryptography(new BouncyCryptography())
|
.cryptography(new BouncyCryptography())
|
||||||
|
.listener(System.out::println)
|
||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
This creates a simple context using a H2 database that will be created in the user's home directory. Next you'll need to
|
This creates a simple context using a H2 database that will be created in the user's home directory. In the listener you decide what happens when a message arrives. If you can't use lambdas, you may instead write
|
||||||
start the context and decide what happens if a message arrives:
|
|
||||||
```Java
|
```Java
|
||||||
ctx.startup(new BitmessageContext.Listener() {
|
.listener(new BitmessageContext.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void receive(Plaintext plaintext) {
|
public void receive(Plaintext plaintext) {
|
||||||
// TODO: Notify the user
|
// TODO: Notify the user
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Next you'll need to start the context:
|
||||||
|
```Java
|
||||||
|
ctx.startup()
|
||||||
```
|
```
|
||||||
Then you might want to create an identity
|
Then you might want to create an identity
|
||||||
```Java
|
```Java
|
||||||
@ -83,3 +116,22 @@ to which you can send some messages
|
|||||||
```Java
|
```Java
|
||||||
ctx.send(identity, contact, "Test", "Hello Chris, this is a message.");
|
ctx.send(identity, contact, "Test", "Hello Chris, this is a message.");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Housekeeping
|
||||||
|
|
||||||
|
As Bitmessage stores all currently valid messages, we'll need to delete expired objects from time to time:
|
||||||
|
```Java
|
||||||
|
ctx.cleanup();
|
||||||
|
```
|
||||||
|
If the client runs all the time, it might be a good idea to do this daily or at least weekly. Otherwise, you might just want to clean up on shutdown.
|
||||||
|
|
||||||
|
Also, if some messages weren't acknowledged when it expired, they can be resent:
|
||||||
|
```Java
|
||||||
|
ctx.resendUnacknowledgedMessages();
|
||||||
|
```
|
||||||
|
This could be triggered periodically, or manually by the user. Please be aware that _if_ there is a message to resend, proof of work needs to be calculated, so to not annoy your users you might not want to trigger it on shutdown. As the client might have been offline for some time, it might as well be wise to wait until it caught up downloading new messages before resending those messages, after all they might be acknowledged by now.
|
||||||
|
|
||||||
|
There probably won't happen extremely bad things if you don't - at least not more than otherwise - but you can properly shutdown the network connection by calling
|
||||||
|
```Java
|
||||||
|
ctx.shutdown();
|
||||||
|
```
|
||||||
|
85
build.gradle
85
build.gradle
@ -1,16 +1,38 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.1.4-3'
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plugins {
|
||||||
|
id 'com.github.ben-manes.versions' version '0.15.0'
|
||||||
|
id "io.spring.dependency-management" version "1.0.3.RELEASE"
|
||||||
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'maven'
|
apply plugin: 'maven'
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
apply plugin: 'jacoco'
|
||||||
|
apply plugin: 'gitflow-version'
|
||||||
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
apply plugin: 'com.github.ben-manes.versions'
|
||||||
|
|
||||||
sourceCompatibility = 1.7
|
sourceCompatibility = 1.7
|
||||||
|
targetCompatibility = 1.7
|
||||||
group = 'ch.dissem.jabit'
|
group = 'ch.dissem.jabit'
|
||||||
version = '1.0.1'
|
|
||||||
|
|
||||||
ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7"
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@ -29,12 +51,28 @@ subprojects {
|
|||||||
from sourceSets.main.allSource
|
from sourceSets.main.allSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.6"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions.jvmTarget = "1.6"
|
||||||
|
}
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
archives javadocJar, sourcesJar
|
archives javadocJar, sourcesJar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes 'Implementation-Title': "Jabit ${project.name.capitalize()}",
|
||||||
|
'Implementation-Version': version
|
||||||
|
}
|
||||||
|
baseName "jabit-${project.name}"
|
||||||
|
}
|
||||||
|
|
||||||
signing {
|
signing {
|
||||||
required { isReleaseVersion && project.getProperties().get("signing.keyId")?.length() > 0 }
|
required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 }
|
||||||
sign configurations.archives
|
sign configurations.archives
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,4 +117,41 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
reports {
|
||||||
|
xml.enabled = true
|
||||||
|
html.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check.dependsOn jacocoTestReport
|
||||||
|
|
||||||
|
dependencyManagement {
|
||||||
|
dependencies {
|
||||||
|
dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
|
||||||
|
entry 'kotlin-stdlib-jre7'
|
||||||
|
entry 'kotlin-reflect'
|
||||||
|
}
|
||||||
|
dependencySet(group: 'org.slf4j', version: '1.7.25') {
|
||||||
|
entry 'slf4j-api'
|
||||||
|
entry 'slf4j-simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependency 'ch.dissem.msgpack:msgpack:2.0.0'
|
||||||
|
dependency 'org.bouncycastle:bcprov-jdk15on:1.57'
|
||||||
|
dependency 'com.madgag.spongycastle:prov:1.56.0.0'
|
||||||
|
dependency 'org.apache.commons:commons-lang3:3.6'
|
||||||
|
dependency 'org.flywaydb:flyway-core:4.2.0'
|
||||||
|
dependency 'com.beust:klaxon:0.31'
|
||||||
|
|
||||||
|
dependency 'args4j:args4j:2.33'
|
||||||
|
dependency 'org.ini4j:ini4j:0.5.4'
|
||||||
|
dependency 'com.h2database:h2:1.4.196'
|
||||||
|
|
||||||
|
dependency 'junit:junit:4.12'
|
||||||
|
dependency 'org.hamcrest:hamcrest-library:1.3'
|
||||||
|
dependency 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package ch.dissem.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the version as follows:
|
||||||
|
* <ul>
|
||||||
|
* <li>If the branch is 'master', the version is set to the latest tag (which is expected to be set by Git flow)</li>
|
||||||
|
* <li>Otherwise, the version is set to the branch name, with '-SNAPSHOT' appended</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
class GitFlowVersion implements Plugin<Project> {
|
||||||
|
def getBranch(Project project) {
|
||||||
|
def stdout = new ByteArrayOutputStream()
|
||||||
|
project.exec {
|
||||||
|
commandLine 'git', 'rev-parse', '--abbrev-ref', 'HEAD'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return stdout.toString().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
def getTag(Project project) {
|
||||||
|
def stdout = new ByteArrayOutputStream()
|
||||||
|
project.exec {
|
||||||
|
commandLine 'git', 'describe', '--abbrev=0'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return stdout.toString().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
def isRelease(Project project) {
|
||||||
|
return "master" == getBranch(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
def getVersion(Project project) {
|
||||||
|
if (project.ext.isRelease) {
|
||||||
|
return getTag(project)
|
||||||
|
} else {
|
||||||
|
def branch = getBranch(project)
|
||||||
|
if ("develop" == branch) {
|
||||||
|
return "development-SNAPSHOT"
|
||||||
|
}
|
||||||
|
return branch.replaceAll("/", "-") + "-SNAPSHOT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void apply(Project project) {
|
||||||
|
project.ext.isRelease = isRelease(project)
|
||||||
|
project.version = getVersion(project)
|
||||||
|
|
||||||
|
project.task('version') {
|
||||||
|
doLast { println "Version deduced from git: '${project.version}'" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
implementation-class=ch.dissem.gradle.GitFlowVersion
|
@ -24,8 +24,28 @@ artifacts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
compile 'org.slf4j:slf4j-api'
|
||||||
testCompile 'junit:junit:4.11'
|
compile 'ch.dissem.msgpack:msgpack:1.0.0'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'junit:junit:4.12'
|
||||||
|
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||||
|
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||||
testCompile project(':cryptography-bc')
|
testCompile project(':cryptography-bc')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def generatedResources = "${project.buildDir}/generated-resources/main"
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
output.dir(generatedResources, builtBy: 'generateVersionInfo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task('generateVersionInfo') {
|
||||||
|
doLast {
|
||||||
|
def dir = new File(generatedResources)
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkdirs()
|
||||||
|
}
|
||||||
|
def file = new File(generatedResources, "version")
|
||||||
|
file.write(project.version.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,435 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.*;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Broadcast;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.ports.*;
|
|
||||||
import ch.dissem.bitmessage.utils.Property;
|
|
||||||
import ch.dissem.bitmessage.utils.TTL;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Use this class if you want to create a Bitmessage client.</p>
|
|
||||||
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
|
||||||
* <ul>
|
|
||||||
* <li>addressRepo</li>
|
|
||||||
* <li>inventory</li>
|
|
||||||
* <li>nodeRegistry</li>
|
|
||||||
* <li>networkHandler</li>
|
|
||||||
* <li>messageRepo</li>
|
|
||||||
* <li>streams</li>
|
|
||||||
* </ul>
|
|
||||||
* <p>The default implementations in the different module builds can be used.</p>
|
|
||||||
* <p>The port defaults to 8444 (the default Bitmessage port)</p>
|
|
||||||
*/
|
|
||||||
public class BitmessageContext {
|
|
||||||
public static final int CURRENT_VERSION = 3;
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
|
|
||||||
|
|
||||||
private final ExecutorService pool;
|
|
||||||
|
|
||||||
private final InternalContext ctx;
|
|
||||||
|
|
||||||
private final Listener listener;
|
|
||||||
private final NetworkHandler.MessageListener networkListener;
|
|
||||||
|
|
||||||
private final boolean sendPubkeyOnIdentityCreation;
|
|
||||||
|
|
||||||
private BitmessageContext(Builder builder) {
|
|
||||||
ctx = new InternalContext(builder);
|
|
||||||
listener = builder.listener;
|
|
||||||
networkListener = new DefaultMessageListener(ctx, listener);
|
|
||||||
|
|
||||||
// As this thread is used for parts that do POW, which itself uses parallel threads, only
|
|
||||||
// one should be executed at any time.
|
|
||||||
pool = Executors.newFixedThreadPool(1);
|
|
||||||
|
|
||||||
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
|
||||||
|
|
||||||
new Timer().schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ctx.getProofOfWorkService().doMissingProofOfWork();
|
|
||||||
}
|
|
||||||
}, 30_000); // After 30 seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressRepository addresses() {
|
|
||||||
return ctx.getAddressRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageRepository messages() {
|
|
||||||
return ctx.getMessageRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
|
|
||||||
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
|
||||||
shorter,
|
|
||||||
ctx.getStreams()[0],
|
|
||||||
ctx.getNetworkNonceTrialsPerByte(),
|
|
||||||
ctx.getNetworkExtraBytes(),
|
|
||||||
features
|
|
||||||
));
|
|
||||||
ctx.getAddressRepository().save(identity);
|
|
||||||
if (sendPubkeyOnIdentityCreation) {
|
|
||||||
pool.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ctx.sendPubkey(identity, identity.getStream());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addDistributedMailingList(String address, String alias) {
|
|
||||||
// TODO
|
|
||||||
throw new RuntimeException("not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
|
|
||||||
Plaintext msg = new Plaintext.Builder(BROADCAST)
|
|
||||||
.from(from)
|
|
||||||
.message(subject, message)
|
|
||||||
.build();
|
|
||||||
send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) {
|
|
||||||
if (from.getPrivateKey() == null) {
|
|
||||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
|
||||||
}
|
|
||||||
Plaintext msg = new Plaintext.Builder(MSG)
|
|
||||||
.from(from)
|
|
||||||
.to(to)
|
|
||||||
.message(subject, message)
|
|
||||||
.labels(messages().getLabels(Label.Type.SENT))
|
|
||||||
.build();
|
|
||||||
send(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final Plaintext msg) {
|
|
||||||
if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
|
|
||||||
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
|
|
||||||
}
|
|
||||||
pool.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
BitmessageAddress to = msg.getTo();
|
|
||||||
if (to != null) {
|
|
||||||
if (to.getPubkey() == null) {
|
|
||||||
LOG.info("Public key is missing from recipient. Requesting.");
|
|
||||||
ctx.requestPubkey(to);
|
|
||||||
}
|
|
||||||
if (to.getPubkey() == null) {
|
|
||||||
msg.setStatus(PUBKEY_REQUESTED);
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (to == null || to.getPubkey() != null) {
|
|
||||||
LOG.info("Sending message.");
|
|
||||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
ctx.send(
|
|
||||||
msg.getFrom(),
|
|
||||||
to,
|
|
||||||
new Msg(msg),
|
|
||||||
TTL.msg()
|
|
||||||
);
|
|
||||||
msg.setStatus(SENT);
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startup() {
|
|
||||||
ctx.getNetworkHandler().start(networkListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
ctx.getNetworkHandler().stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
|
||||||
* @param port of the trusted host, default is 8444
|
|
||||||
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
|
|
||||||
* if not all objects were fetched
|
|
||||||
* @param wait waits for the synchronization thread to finish
|
|
||||||
*/
|
|
||||||
public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) {
|
|
||||||
Future<?> future = ctx.getNetworkHandler().synchronize(host, port, networkListener, timeoutInSeconds);
|
|
||||||
if (wait) {
|
|
||||||
try {
|
|
||||||
future.get();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.");
|
|
||||||
future.cancel(true);
|
|
||||||
} catch (CancellationException | ExecutionException e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
|
||||||
* the response, which in turn is expected to be a {@link CustomMessage}.
|
|
||||||
*
|
|
||||||
* @param server the node's address
|
|
||||||
* @param port the node's port
|
|
||||||
* @param request the request
|
|
||||||
* @return the response
|
|
||||||
*/
|
|
||||||
public CustomMessage send(InetAddress server, int port, CustomMessage request) {
|
|
||||||
return ctx.getNetworkHandler().send(server, port, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cleanup() {
|
|
||||||
ctx.getInventory().cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRunning() {
|
|
||||||
return ctx.getNetworkHandler().isRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addContact(BitmessageAddress contact) {
|
|
||||||
ctx.getAddressRepository().save(contact);
|
|
||||||
if (contact.getPubkey() == null) {
|
|
||||||
ctx.requestPubkey(contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSubscribtion(BitmessageAddress address) {
|
|
||||||
address.setSubscribed(true);
|
|
||||||
ctx.getAddressRepository().save(address);
|
|
||||||
tryToFindBroadcastsForAddress(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
|
|
||||||
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
|
|
||||||
try {
|
|
||||||
Broadcast broadcast = (Broadcast) object.getPayload();
|
|
||||||
broadcast.decrypt(address);
|
|
||||||
listener.receive(broadcast.getPlaintext());
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property status() {
|
|
||||||
return new Property("status", null,
|
|
||||||
ctx.getNetworkHandler().getNetworkStatus()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link InternalContext} - normally you wouldn't need it,
|
|
||||||
* unless you are doing something crazy with the protocol.
|
|
||||||
*/
|
|
||||||
public InternalContext internals() {
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void receive(Plaintext plaintext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
int port = 8444;
|
|
||||||
Inventory inventory;
|
|
||||||
NodeRegistry nodeRegistry;
|
|
||||||
NetworkHandler networkHandler;
|
|
||||||
AddressRepository addressRepo;
|
|
||||||
MessageRepository messageRepo;
|
|
||||||
ProofOfWorkRepository proofOfWorkRepository;
|
|
||||||
ProofOfWorkEngine proofOfWorkEngine;
|
|
||||||
Cryptography cryptography;
|
|
||||||
MessageCallback messageCallback;
|
|
||||||
CustomCommandHandler customCommandHandler;
|
|
||||||
Listener listener;
|
|
||||||
int connectionLimit = 150;
|
|
||||||
long connectionTTL = 30 * MINUTE;
|
|
||||||
boolean sendPubkeyOnIdentityCreation = true;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder port(int port) {
|
|
||||||
this.port = port;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder inventory(Inventory inventory) {
|
|
||||||
this.inventory = inventory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nodeRegistry(NodeRegistry nodeRegistry) {
|
|
||||||
this.nodeRegistry = nodeRegistry;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder networkHandler(NetworkHandler networkHandler) {
|
|
||||||
this.networkHandler = networkHandler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addressRepo(AddressRepository addressRepo) {
|
|
||||||
this.addressRepo = addressRepo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder messageRepo(MessageRepository messageRepo) {
|
|
||||||
this.messageRepo = messageRepo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
|
|
||||||
this.proofOfWorkRepository = proofOfWorkRepository;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder cryptography(Cryptography cryptography) {
|
|
||||||
this.cryptography = cryptography;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder messageCallback(MessageCallback callback) {
|
|
||||||
this.messageCallback = callback;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder customCommandHandler(CustomCommandHandler handler) {
|
|
||||||
this.customCommandHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
|
|
||||||
this.proofOfWorkEngine = proofOfWorkEngine;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder listener(Listener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder connectionLimit(int connectionLimit) {
|
|
||||||
this.connectionLimit = connectionLimit;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder connectionTTL(int hours) {
|
|
||||||
this.connectionTTL = hours * HOUR;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
|
||||||
* this behaviour might not be desirable.
|
|
||||||
*/
|
|
||||||
public Builder doNotSendPubkeyOnIdentityCreation() {
|
|
||||||
this.sendPubkeyOnIdentityCreation = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time to live in seconds for public keys the client sends. Defaults to the maximum of 28 days,
|
|
||||||
* but on weak devices smaller values might be desirable.
|
|
||||||
* <p>
|
|
||||||
* Please be aware that this might cause some problems where you can't receive a message (the
|
|
||||||
* sender can't receive your public key) in some special situations. Also note that it's probably
|
|
||||||
* not a good idea to set it too low.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public Builder pubkeyTTL(long days) {
|
|
||||||
if (days < 0 || days > 28 * DAY) throw new IllegalArgumentException("TTL must be between 1 and 28 days");
|
|
||||||
TTL.pubkey(days);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageContext build() {
|
|
||||||
nonNull("inventory", inventory);
|
|
||||||
nonNull("nodeRegistry", nodeRegistry);
|
|
||||||
nonNull("networkHandler", networkHandler);
|
|
||||||
nonNull("addressRepo", addressRepo);
|
|
||||||
nonNull("messageRepo", messageRepo);
|
|
||||||
nonNull("proofOfWorkRepo", proofOfWorkRepository);
|
|
||||||
if (proofOfWorkEngine == null) {
|
|
||||||
proofOfWorkEngine = new MultiThreadedPOWEngine();
|
|
||||||
}
|
|
||||||
if (messageCallback == null) {
|
|
||||||
messageCallback = new MessageCallback() {
|
|
||||||
@Override
|
|
||||||
public void proofOfWorkStarted(ObjectPayload message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void proofOfWorkCompleted(ObjectPayload message) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageOffered(ObjectPayload message, InventoryVector iv) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageAcknowledged(InventoryVector iv) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (customCommandHandler == null) {
|
|
||||||
customCommandHandler = new CustomCommandHandler() {
|
|
||||||
@Override
|
|
||||||
public MessagePayload handle(CustomMessage request) {
|
|
||||||
throw new RuntimeException("Received custom request, but no custom command handler configured.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new BitmessageContext(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void nonNull(String name, Object o) {
|
|
||||||
if (o == null) throw new IllegalStateException(name + " must not be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,162 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.*;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.ports.NetworkHandler;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
|
|
||||||
class DefaultMessageListener implements NetworkHandler.MessageListener {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class);
|
|
||||||
private final InternalContext ctx;
|
|
||||||
private final BitmessageContext.Listener listener;
|
|
||||||
|
|
||||||
public DefaultMessageListener(InternalContext context, BitmessageContext.Listener listener) {
|
|
||||||
this.ctx = context;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive(ObjectMessage object) throws IOException {
|
|
||||||
ObjectPayload payload = object.getPayload();
|
|
||||||
if (payload.getType() == null) return;
|
|
||||||
|
|
||||||
switch (payload.getType()) {
|
|
||||||
case GET_PUBKEY: {
|
|
||||||
receive(object, (GetPubkey) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PUBKEY: {
|
|
||||||
receive(object, (Pubkey) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MSG: {
|
|
||||||
receive(object, (Msg) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BROADCAST: {
|
|
||||||
receive(object, (Broadcast) payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
|
|
||||||
BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
|
|
||||||
if (identity != null && identity.getPrivateKey() != null) {
|
|
||||||
LOG.info("Got pubkey request for identity " + identity);
|
|
||||||
// FIXME: only send pubkey if it wasn't sent in the last 28 days
|
|
||||||
ctx.sendPubkey(identity, object.getStream());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, Pubkey pubkey) throws IOException {
|
|
||||||
BitmessageAddress address;
|
|
||||||
try {
|
|
||||||
if (pubkey instanceof V4Pubkey) {
|
|
||||||
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
|
|
||||||
address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
|
|
||||||
if (address != null) {
|
|
||||||
v4Pubkey.decrypt(address.getPublicDecryptionKey());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
address = ctx.getAddressRepository().findContact(pubkey.getRipe());
|
|
||||||
}
|
|
||||||
if (address != null) {
|
|
||||||
updatePubkey(address, pubkey);
|
|
||||||
}
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePubkey(BitmessageAddress address, Pubkey pubkey){
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
LOG.info("Got pubkey for contact " + address);
|
|
||||||
ctx.getAddressRepository().save(address);
|
|
||||||
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
|
|
||||||
LOG.info("Sending " + messages.size() + " messages for contact " + address);
|
|
||||||
for (Plaintext msg : messages) {
|
|
||||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
ctx.send(
|
|
||||||
msg.getFrom(),
|
|
||||||
msg.getTo(),
|
|
||||||
new Msg(msg),
|
|
||||||
+2 * DAY
|
|
||||||
);
|
|
||||||
msg.setStatus(SENT);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, Msg msg) throws IOException {
|
|
||||||
for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
|
|
||||||
try {
|
|
||||||
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
|
|
||||||
msg.getPlaintext().setTo(identity);
|
|
||||||
if (!object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey())) {
|
|
||||||
LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
|
|
||||||
} else {
|
|
||||||
msg.getPlaintext().setStatus(RECEIVED);
|
|
||||||
msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
|
|
||||||
msg.getPlaintext().setInventoryVector(object.getInventoryVector());
|
|
||||||
ctx.getMessageRepository().save(msg.getPlaintext());
|
|
||||||
listener.receive(msg.getPlaintext());
|
|
||||||
updatePubkey(msg.getPlaintext().getFrom(), msg.getPlaintext().getFrom().getPubkey());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
|
|
||||||
byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
|
|
||||||
for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
|
|
||||||
if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
broadcast.decrypt(subscription.getPublicDecryptionKey());
|
|
||||||
if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) {
|
|
||||||
LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
|
|
||||||
} else {
|
|
||||||
broadcast.getPlaintext().setStatus(RECEIVED);
|
|
||||||
broadcast.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD));
|
|
||||||
broadcast.getPlaintext().setInventoryVector(object.getInventoryVector());
|
|
||||||
ctx.getMessageRepository().save(broadcast.getPlaintext());
|
|
||||||
listener.receive(broadcast.getPlaintext());
|
|
||||||
updatePubkey(broadcast.getPlaintext().getFrom(), broadcast.getPlaintext().getFrom().getPubkey());
|
|
||||||
}
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.*;
|
|
||||||
import ch.dissem.bitmessage.ports.*;
|
|
||||||
import ch.dissem.bitmessage.utils.Singleton;
|
|
||||||
import ch.dissem.bitmessage.utils.TTL;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal context should normally only be used for port implementations. If you need it in your client
|
|
||||||
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
|
|
||||||
* get extended.
|
|
||||||
* <p>
|
|
||||||
* On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class InternalContext {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
|
|
||||||
|
|
||||||
private final Cryptography cryptography;
|
|
||||||
private final Inventory inventory;
|
|
||||||
private final NodeRegistry nodeRegistry;
|
|
||||||
private final NetworkHandler networkHandler;
|
|
||||||
private final AddressRepository addressRepository;
|
|
||||||
private final MessageRepository messageRepository;
|
|
||||||
private final ProofOfWorkRepository proofOfWorkRepository;
|
|
||||||
private final ProofOfWorkEngine proofOfWorkEngine;
|
|
||||||
private final MessageCallback messageCallback;
|
|
||||||
private final CustomCommandHandler customCommandHandler;
|
|
||||||
private final ProofOfWorkService proofOfWorkService;
|
|
||||||
|
|
||||||
private final TreeSet<Long> streams = new TreeSet<>();
|
|
||||||
private final int port;
|
|
||||||
private final long clientNonce;
|
|
||||||
private final long networkNonceTrialsPerByte = 1000;
|
|
||||||
private final long networkExtraBytes = 1000;
|
|
||||||
private long connectionTTL;
|
|
||||||
private int connectionLimit;
|
|
||||||
|
|
||||||
public InternalContext(BitmessageContext.Builder builder) {
|
|
||||||
this.cryptography = builder.cryptography;
|
|
||||||
this.inventory = builder.inventory;
|
|
||||||
this.nodeRegistry = builder.nodeRegistry;
|
|
||||||
this.networkHandler = builder.networkHandler;
|
|
||||||
this.addressRepository = builder.addressRepo;
|
|
||||||
this.messageRepository = builder.messageRepo;
|
|
||||||
this.proofOfWorkRepository = builder.proofOfWorkRepository;
|
|
||||||
this.proofOfWorkService = new ProofOfWorkService();
|
|
||||||
this.proofOfWorkEngine = builder.proofOfWorkEngine;
|
|
||||||
this.clientNonce = cryptography.randomNonce();
|
|
||||||
this.messageCallback = builder.messageCallback;
|
|
||||||
this.customCommandHandler = builder.customCommandHandler;
|
|
||||||
this.port = builder.port;
|
|
||||||
this.connectionLimit = builder.connectionLimit;
|
|
||||||
this.connectionTTL = builder.connectionTTL;
|
|
||||||
|
|
||||||
Singleton.initialize(cryptography);
|
|
||||||
|
|
||||||
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
|
|
||||||
for (BitmessageAddress address : addressRepository.getIdentities()) {
|
|
||||||
streams.add(address.getStream());
|
|
||||||
}
|
|
||||||
for (BitmessageAddress address : addressRepository.getSubscriptions()) {
|
|
||||||
streams.add(address.getStream());
|
|
||||||
}
|
|
||||||
if (streams.isEmpty()) {
|
|
||||||
streams.add(1L);
|
|
||||||
}
|
|
||||||
|
|
||||||
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
|
|
||||||
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine,
|
|
||||||
messageCallback, customCommandHandler);
|
|
||||||
for (BitmessageAddress identity : addressRepository.getIdentities()) {
|
|
||||||
streams.add(identity.getStream());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(Object... objects) {
|
|
||||||
for (Object o : objects) {
|
|
||||||
if (o instanceof ContextHolder) {
|
|
||||||
((ContextHolder) o).setContext(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cryptography getCryptography() {
|
|
||||||
return cryptography;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Inventory getInventory() {
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeRegistry getNodeRegistry() {
|
|
||||||
return nodeRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkHandler getNetworkHandler() {
|
|
||||||
return networkHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressRepository getAddressRepository() {
|
|
||||||
return addressRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageRepository getMessageRepository() {
|
|
||||||
return messageRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProofOfWorkRepository getProofOfWorkRepository() {
|
|
||||||
return proofOfWorkRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProofOfWorkEngine getProofOfWorkEngine() {
|
|
||||||
return proofOfWorkEngine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProofOfWorkService getProofOfWorkService() {
|
|
||||||
return proofOfWorkService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long[] getStreams() {
|
|
||||||
long[] result = new long[streams.size()];
|
|
||||||
int i = 0;
|
|
||||||
for (long stream : streams) {
|
|
||||||
result[i++] = stream;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNetworkNonceTrialsPerByte() {
|
|
||||||
return networkNonceTrialsPerByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNetworkExtraBytes() {
|
|
||||||
return networkExtraBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
|
|
||||||
final long timeToLive) {
|
|
||||||
try {
|
|
||||||
if (to == null) to = from;
|
|
||||||
long expires = UnixTime.now(+timeToLive);
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
final ObjectMessage object = new ObjectMessage.Builder()
|
|
||||||
.stream(to.getStream())
|
|
||||||
.expiresTime(expires)
|
|
||||||
.payload(payload)
|
|
||||||
.build();
|
|
||||||
if (object.isSigned()) {
|
|
||||||
object.sign(from.getPrivateKey());
|
|
||||||
}
|
|
||||||
if (payload instanceof Broadcast) {
|
|
||||||
((Broadcast) payload).encrypt();
|
|
||||||
} else if (payload instanceof Encrypted) {
|
|
||||||
object.encrypt(to.getPubkey());
|
|
||||||
}
|
|
||||||
messageCallback.proofOfWorkStarted(payload);
|
|
||||||
proofOfWorkService.doProofOfWork(to, object);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
|
|
||||||
try {
|
|
||||||
long expires = UnixTime.now(TTL.pubkey());
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
final ObjectMessage response = new ObjectMessage.Builder()
|
|
||||||
.stream(targetStream)
|
|
||||||
.expiresTime(expires)
|
|
||||||
.payload(identity.getPubkey())
|
|
||||||
.build();
|
|
||||||
response.sign(identity.getPrivateKey());
|
|
||||||
response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey()));
|
|
||||||
messageCallback.proofOfWorkStarted(identity.getPubkey());
|
|
||||||
// TODO: remember that the pubkey is just about to be sent, and on which stream!
|
|
||||||
proofOfWorkService.doProofOfWork(response);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback
|
|
||||||
* for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB.
|
|
||||||
*/
|
|
||||||
public void requestPubkey(final BitmessageAddress contact) {
|
|
||||||
BitmessageAddress stored = addressRepository.getAddress(contact.getAddress());
|
|
||||||
|
|
||||||
tryToFindMatchingPubkey(contact);
|
|
||||||
if (contact.getPubkey() != null) {
|
|
||||||
if (stored != null) {
|
|
||||||
stored.setPubkey(contact.getPubkey());
|
|
||||||
addressRepository.save(stored);
|
|
||||||
} else {
|
|
||||||
addressRepository.save(contact);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stored == null) {
|
|
||||||
addressRepository.save(contact);
|
|
||||||
}
|
|
||||||
|
|
||||||
long expires = UnixTime.now(TTL.getpubkey());
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
final ObjectMessage request = new ObjectMessage.Builder()
|
|
||||||
.stream(contact.getStream())
|
|
||||||
.expiresTime(expires)
|
|
||||||
.payload(new GetPubkey(contact))
|
|
||||||
.build();
|
|
||||||
messageCallback.proofOfWorkStarted(request.getPayload());
|
|
||||||
proofOfWorkService.doProofOfWork(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToFindMatchingPubkey(BitmessageAddress address) {
|
|
||||||
BitmessageAddress stored = addressRepository.getAddress(address.getAddress());
|
|
||||||
if (stored != null) {
|
|
||||||
address.setAlias(stored.getAlias());
|
|
||||||
address.setSubscribed(stored.isSubscribed());
|
|
||||||
}
|
|
||||||
for (ObjectMessage object : inventory.getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) {
|
|
||||||
try {
|
|
||||||
Pubkey pubkey = (Pubkey) object.getPayload();
|
|
||||||
if (address.getVersion() == 4) {
|
|
||||||
V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
|
|
||||||
if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) {
|
|
||||||
v4Pubkey.decrypt(address.getPublicDecryptionKey());
|
|
||||||
if (object.isSignatureValid(v4Pubkey)) {
|
|
||||||
address.setPubkey(v4Pubkey);
|
|
||||||
addressRepository.save(address);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
LOG.info("Found pubkey for " + address + " but signature is invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
addressRepository.save(address);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getClientNonce() {
|
|
||||||
return clientNonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getConnectionTTL() {
|
|
||||||
return connectionTTL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getConnectionLimit() {
|
|
||||||
return connectionLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomCommandHandler getCustomCommandHandler() {
|
|
||||||
return customCommandHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ContextHolder {
|
|
||||||
void setContext(InternalContext context);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for message sending events, mostly so the user can be notified when POW is done.
|
|
||||||
*/
|
|
||||||
public interface MessageCallback {
|
|
||||||
/**
|
|
||||||
* Called before calculation of proof of work begins.
|
|
||||||
*/
|
|
||||||
void proofOfWorkStarted(ObjectPayload message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after calculation of proof of work finished.
|
|
||||||
*/
|
|
||||||
void proofOfWorkCompleted(ObjectPayload message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once the message is offered to the network. Please note that this doesn't mean the message was sent,
|
|
||||||
* if the client is not connected to the network it's just stored in the inventory.
|
|
||||||
* <p>
|
|
||||||
* Also, please note that this is where the original payload as well as the {@link InventoryVector} of the sent
|
|
||||||
* message is available. If the callback needs the IV for some reason, it should be retrieved here. (Plaintext
|
|
||||||
* and Broadcast messages will have their IV property set automatically though.)
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
void messageOffered(ObjectPayload message, InventoryVector iv);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This isn't called yet, as ACK messages aren't being processed yet. Also, this is only relevant for Plaintext
|
|
||||||
* messages.
|
|
||||||
*/
|
|
||||||
void messageAcknowledged(InventoryVector iv);
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.Cryptography;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
|
|
||||||
|
|
||||||
private Cryptography cryptography;
|
|
||||||
private InternalContext ctx;
|
|
||||||
private ProofOfWorkRepository powRepo;
|
|
||||||
private MessageRepository messageRepo;
|
|
||||||
|
|
||||||
public void doMissingProofOfWork() {
|
|
||||||
List<byte[]> items = powRepo.getItems();
|
|
||||||
if (items.isEmpty()) return;
|
|
||||||
|
|
||||||
LOG.info("Doing POW for " + items.size() + " tasks.");
|
|
||||||
for (byte[] initialHash : items) {
|
|
||||||
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
|
|
||||||
cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doProofOfWork(ObjectMessage object) {
|
|
||||||
doProofOfWork(null, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
|
|
||||||
Pubkey pubkey = recipient == null ? null : recipient.getPubkey();
|
|
||||||
|
|
||||||
long nonceTrialsPerByte = pubkey == null ? ctx.getNetworkNonceTrialsPerByte() : pubkey.getNonceTrialsPerByte();
|
|
||||||
long extraBytes = pubkey == null ? ctx.getNetworkExtraBytes() : pubkey.getExtraBytes();
|
|
||||||
|
|
||||||
powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
|
|
||||||
if (object.getPayload() instanceof PlaintextHolder) {
|
|
||||||
Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
|
|
||||||
plaintext.setInitialHash(cryptography.getInitialHash(object));
|
|
||||||
messageRepo.save(plaintext);
|
|
||||||
}
|
|
||||||
cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
ObjectMessage object = powRepo.getItem(initialHash).object;
|
|
||||||
object.setNonce(nonce);
|
|
||||||
// messageCallback.proofOfWorkCompleted(payload);
|
|
||||||
Plaintext plaintext = messageRepo.getMessage(initialHash);
|
|
||||||
if (plaintext != null) {
|
|
||||||
plaintext.setInventoryVector(object.getInventoryVector());
|
|
||||||
messageRepo.save(plaintext);
|
|
||||||
}
|
|
||||||
ctx.getInventory().storeObject(object);
|
|
||||||
ctx.getProofOfWorkRepository().removeObject(initialHash);
|
|
||||||
ctx.getNetworkHandler().offer(object.getInventoryVector());
|
|
||||||
// messageCallback.messageOffered(payload, object.getInventoryVector());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.cryptography = security();
|
|
||||||
this.powRepo = ctx.getProofOfWorkRepository();
|
|
||||||
this.messageRepo = ctx.getMessageRepository();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'addr' command holds a list of known active Bitmessage nodes.
|
|
||||||
*/
|
|
||||||
public class Addr implements MessagePayload {
|
|
||||||
private final List<NetworkAddress> addresses;
|
|
||||||
|
|
||||||
private Addr(Builder builder) {
|
|
||||||
addresses = builder.addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.ADDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<NetworkAddress> getAddresses() {
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
Encode.varInt(addresses.size(), stream);
|
|
||||||
for (NetworkAddress address : addresses) {
|
|
||||||
address.write(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<NetworkAddress> addresses = new ArrayList<NetworkAddress>();
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addresses(Collection<NetworkAddress> addresses){
|
|
||||||
this.addresses.addAll(addresses);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addAddress(final NetworkAddress address) {
|
|
||||||
this.addresses.add(address);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Addr build() {
|
|
||||||
return new Addr(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,223 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
||||||
import ch.dissem.bitmessage.utils.Base58;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.varInt;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
|
|
||||||
* holding private keys.
|
|
||||||
*/
|
|
||||||
public class BitmessageAddress implements Serializable {
|
|
||||||
private final long version;
|
|
||||||
private final long stream;
|
|
||||||
private final byte[] ripe;
|
|
||||||
private final byte[] tag;
|
|
||||||
/**
|
|
||||||
* Used for V4 address encryption. It's easier to just create it regardless of address version.
|
|
||||||
*/
|
|
||||||
private final byte[] publicDecryptionKey;
|
|
||||||
|
|
||||||
private String address;
|
|
||||||
|
|
||||||
private PrivateKey privateKey;
|
|
||||||
private Pubkey pubkey;
|
|
||||||
|
|
||||||
private String alias;
|
|
||||||
private boolean subscribed;
|
|
||||||
|
|
||||||
BitmessageAddress(long version, long stream, byte[] ripe) {
|
|
||||||
try {
|
|
||||||
this.version = version;
|
|
||||||
this.stream = stream;
|
|
||||||
this.ripe = ripe;
|
|
||||||
|
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
||||||
Encode.varInt(version, os);
|
|
||||||
Encode.varInt(stream, os);
|
|
||||||
if (version < 4) {
|
|
||||||
byte[] checksum = security().sha512(os.toByteArray(), ripe);
|
|
||||||
this.tag = null;
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
} else {
|
|
||||||
// for tag and decryption key, the checksum has to be created with 0x00 padding
|
|
||||||
byte[] checksum = security().doubleSha512(os.toByteArray(), ripe);
|
|
||||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
}
|
|
||||||
// but for the address and its checksum they need to be stripped
|
|
||||||
int offset = Bytes.numberOfLeadingZeros(ripe);
|
|
||||||
os.write(ripe, offset, ripe.length - offset);
|
|
||||||
byte[] checksum = security().doubleSha512(os.toByteArray());
|
|
||||||
os.write(checksum, 0, 4);
|
|
||||||
this.address = "BM-" + Base58.encode(os.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(Pubkey publicKey) {
|
|
||||||
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
|
|
||||||
this.pubkey = publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(PrivateKey privateKey) {
|
|
||||||
this(privateKey.getPubkey());
|
|
||||||
this.privateKey = privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(String address) {
|
|
||||||
try {
|
|
||||||
this.address = address;
|
|
||||||
byte[] bytes = Base58.decode(address.substring(3));
|
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
this.version = varInt(in, counter);
|
|
||||||
this.stream = varInt(in, counter);
|
|
||||||
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
|
|
||||||
|
|
||||||
// test checksum
|
|
||||||
byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
|
|
||||||
byte[] expectedChecksum = bytes(in, 4);
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
if (expectedChecksum[i] != checksum[i])
|
|
||||||
throw new IllegalArgumentException("Checksum of address failed");
|
|
||||||
}
|
|
||||||
if (version < 4) {
|
|
||||||
checksum = security().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
|
||||||
this.tag = null;
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
} else {
|
|
||||||
checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
|
||||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
|
||||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] calculateTag(long version, long stream, byte[] ripe) {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
Encode.varInt(version, out);
|
|
||||||
Encode.varInt(stream, out);
|
|
||||||
out.write(ripe);
|
|
||||||
return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pubkey getPubkey() {
|
|
||||||
return pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPubkey(Pubkey pubkey) {
|
|
||||||
if (pubkey instanceof V4Pubkey) {
|
|
||||||
if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag()))
|
|
||||||
throw new IllegalArgumentException("Pubkey has incompatible tag");
|
|
||||||
}
|
|
||||||
if (!Arrays.equals(ripe, pubkey.getRipe()))
|
|
||||||
throw new IllegalArgumentException("Pubkey has incompatible ripe");
|
|
||||||
this.pubkey = pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts.
|
|
||||||
*/
|
|
||||||
public byte[] getPublicDecryptionKey() {
|
|
||||||
return publicDecryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey getPrivateKey() {
|
|
||||||
return privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAlias() {
|
|
||||||
return alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAlias(String alias) {
|
|
||||||
this.alias = alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return alias != null ? alias : address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getRipe() {
|
|
||||||
return ripe;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
BitmessageAddress address = (BitmessageAddress) o;
|
|
||||||
return Objects.equals(version, address.version) &&
|
|
||||||
Objects.equals(stream, address.stream) &&
|
|
||||||
Arrays.equals(ripe, address.ripe);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Arrays.hashCode(ripe);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSubscribed() {
|
|
||||||
return subscribed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubscribed(boolean subscribed) {
|
|
||||||
this.subscribed = subscribed;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.varString;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class CustomMessage implements MessagePayload {
|
|
||||||
public static final String COMMAND_ERROR = "ERROR";
|
|
||||||
|
|
||||||
private final String command;
|
|
||||||
private final byte[] data;
|
|
||||||
|
|
||||||
public CustomMessage(String command) {
|
|
||||||
this.command = command;
|
|
||||||
this.data = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomMessage(String command, byte[] data) {
|
|
||||||
this.command = command;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomMessage read(InputStream in, int length) throws IOException {
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.CUSTOM;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomCommand() {
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getData() {
|
|
||||||
if (data != null) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
write(out);
|
|
||||||
return out.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (data != null) {
|
|
||||||
Encode.varString(command, out);
|
|
||||||
out.write(data);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Tried to write custom message without data. " +
|
|
||||||
"Programmer: did you forget to override #write()?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return COMMAND_ERROR.equals(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomMessage error(String message) {
|
|
||||||
try {
|
|
||||||
return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'getdata' command is used to request objects from a node.
|
|
||||||
*/
|
|
||||||
public class GetData implements MessagePayload {
|
|
||||||
List<InventoryVector> inventory;
|
|
||||||
|
|
||||||
private GetData(Builder builder) {
|
|
||||||
inventory = builder.inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.GETDATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<InventoryVector> getInventory() {
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
Encode.varInt(inventory.size(), out);
|
|
||||||
for (InventoryVector iv : inventory) {
|
|
||||||
iv.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<InventoryVector> inventory = new LinkedList<>();
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addInventoryVector(InventoryVector inventoryVector) {
|
|
||||||
this.inventory.add(inventoryVector);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder inventory(List<InventoryVector> inventory) {
|
|
||||||
this.inventory = inventory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetData build() {
|
|
||||||
return new GetData(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
|
||||||
*/
|
|
||||||
public class Inv implements MessagePayload {
|
|
||||||
private List<InventoryVector> inventory;
|
|
||||||
|
|
||||||
private Inv(Builder builder) {
|
|
||||||
inventory = builder.inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<InventoryVector> getInventory() {
|
|
||||||
return inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.INV;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
Encode.varInt(inventory.size(), out);
|
|
||||||
for (InventoryVector iv : inventory) {
|
|
||||||
iv.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<InventoryVector> inventory = new LinkedList<>();
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addInventoryVector(InventoryVector inventoryVector) {
|
|
||||||
this.inventory.add(inventoryVector);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder inventory(List<InventoryVector> inventory) {
|
|
||||||
this.inventory = inventory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Inv build() {
|
|
||||||
return new Inv(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A network message is exchanged between two nodes.
|
|
||||||
*/
|
|
||||||
public class NetworkMessage implements Streamable {
|
|
||||||
/**
|
|
||||||
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
|
|
||||||
*/
|
|
||||||
public final static int MAGIC = 0xE9BEB4D9;
|
|
||||||
public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array();
|
|
||||||
|
|
||||||
private final MessagePayload payload;
|
|
||||||
|
|
||||||
public NetworkMessage(MessagePayload payload) {
|
|
||||||
this.payload = payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First 4 bytes of sha512(payload)
|
|
||||||
*/
|
|
||||||
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
|
|
||||||
byte[] d = security().sha512(bytes);
|
|
||||||
return new byte[]{d[0], d[1], d[2], d[3]};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual data, a message or an object. Not to be confused with objectPayload.
|
|
||||||
*/
|
|
||||||
public MessagePayload getPayload() {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
// magic
|
|
||||||
Encode.int32(MAGIC, out);
|
|
||||||
|
|
||||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
|
||||||
String command = payload.getCommand().name().toLowerCase();
|
|
||||||
out.write(command.getBytes("ASCII"));
|
|
||||||
for (int i = command.length(); i < 12; i++) {
|
|
||||||
out.write('\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteArrayOutputStream payloadStream = new ByteArrayOutputStream();
|
|
||||||
payload.write(payloadStream);
|
|
||||||
byte[] payloadBytes = payloadStream.toByteArray();
|
|
||||||
|
|
||||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
|
||||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
|
||||||
// larger than this.
|
|
||||||
Encode.int32(payloadBytes.length, out);
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
try {
|
|
||||||
out.write(getChecksum(payloadBytes));
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// message payload
|
|
||||||
out.write(payloadBytes);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,233 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'object' command sends an object that is shared throughout the network.
|
|
||||||
*/
|
|
||||||
public class ObjectMessage implements MessagePayload {
|
|
||||||
private byte[] nonce;
|
|
||||||
private long expiresTime;
|
|
||||||
private long objectType;
|
|
||||||
/**
|
|
||||||
* The object's version
|
|
||||||
*/
|
|
||||||
private long version;
|
|
||||||
private long stream;
|
|
||||||
|
|
||||||
private ObjectPayload payload;
|
|
||||||
private byte[] payloadBytes;
|
|
||||||
|
|
||||||
private ObjectMessage(Builder builder) {
|
|
||||||
nonce = builder.nonce;
|
|
||||||
expiresTime = builder.expiresTime;
|
|
||||||
objectType = builder.objectType;
|
|
||||||
version = builder.payload.getVersion();
|
|
||||||
stream = builder.streamNumber;
|
|
||||||
payload = builder.payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.OBJECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getNonce() {
|
|
||||||
return nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNonce(byte[] nonce) {
|
|
||||||
this.nonce = nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExpiresTime() {
|
|
||||||
return expiresTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getType() {
|
|
||||||
return objectType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectPayload getPayload() {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryVector getInventoryVector() {
|
|
||||||
return new InventoryVector(
|
|
||||||
Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEncrypted() {
|
|
||||||
return payload instanceof Encrypted && !((Encrypted) payload).isDecrypted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return payload.isSigned();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getBytesToSign() {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
writeHeaderWithoutNonce(out);
|
|
||||||
payload.writeBytesToSign(out);
|
|
||||||
return out.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sign(PrivateKey key) {
|
|
||||||
if (payload.isSigned()) {
|
|
||||||
payload.setSignature(security().getSignature(getBytesToSign(), key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrypt(PrivateKey key) throws IOException, DecryptionFailedException {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).decrypt(key.getPrivateEncryptionKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrypt(byte[] privateEncryptionKey) throws IOException, DecryptionFailedException {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).decrypt(privateEncryptionKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encrypt(byte[] publicEncryptionKey) throws IOException {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).encrypt(publicEncryptionKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encrypt(Pubkey publicKey) {
|
|
||||||
try {
|
|
||||||
if (payload instanceof Encrypted) {
|
|
||||||
((Encrypted) payload).encrypt(publicKey.getEncryptionKey());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
|
|
||||||
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
|
|
||||||
return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (nonce != null) {
|
|
||||||
out.write(nonce);
|
|
||||||
} else {
|
|
||||||
out.write(new byte[8]);
|
|
||||||
}
|
|
||||||
out.write(getPayloadBytesWithoutNonce());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeHeaderWithoutNonce(OutputStream out) throws IOException {
|
|
||||||
Encode.int64(expiresTime, out);
|
|
||||||
Encode.int32(objectType, out);
|
|
||||||
Encode.varInt(version, out);
|
|
||||||
Encode.varInt(stream, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPayloadBytesWithoutNonce() {
|
|
||||||
try {
|
|
||||||
if (payloadBytes == null) {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
writeHeaderWithoutNonce(out);
|
|
||||||
payload.write(out);
|
|
||||||
payloadBytes = out.toByteArray();
|
|
||||||
}
|
|
||||||
return payloadBytes;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private byte[] nonce;
|
|
||||||
private long expiresTime;
|
|
||||||
private long objectType = -1;
|
|
||||||
private long streamNumber;
|
|
||||||
private ObjectPayload payload;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nonce(byte[] nonce) {
|
|
||||||
this.nonce = nonce;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder expiresTime(long expiresTime) {
|
|
||||||
this.expiresTime = expiresTime;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder objectType(long objectType) {
|
|
||||||
this.objectType = objectType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder objectType(ObjectType objectType) {
|
|
||||||
this.objectType = objectType.getNumber();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(long streamNumber) {
|
|
||||||
this.streamNumber = streamNumber;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder payload(ObjectPayload payload) {
|
|
||||||
this.payload = payload;
|
|
||||||
if (this.objectType == -1)
|
|
||||||
this.objectType = payload.getType().getNumber();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectMessage build() {
|
|
||||||
return new ObjectMessage(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,464 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The unencrypted message to be sent by 'msg' or 'broadcast'.
|
|
||||||
*/
|
|
||||||
public class Plaintext implements Streamable {
|
|
||||||
private final Type type;
|
|
||||||
private final BitmessageAddress from;
|
|
||||||
private final long encoding;
|
|
||||||
private final byte[] message;
|
|
||||||
private final byte[] ack;
|
|
||||||
private Object id;
|
|
||||||
private InventoryVector inventoryVector;
|
|
||||||
private BitmessageAddress to;
|
|
||||||
private byte[] signature;
|
|
||||||
private Status status;
|
|
||||||
private Long sent;
|
|
||||||
private Long received;
|
|
||||||
|
|
||||||
private Set<Label> labels;
|
|
||||||
private byte[] initialHash;
|
|
||||||
|
|
||||||
private Plaintext(Builder builder) {
|
|
||||||
id = builder.id;
|
|
||||||
inventoryVector = builder.inventoryVector;
|
|
||||||
type = builder.type;
|
|
||||||
from = builder.from;
|
|
||||||
to = builder.to;
|
|
||||||
encoding = builder.encoding;
|
|
||||||
message = builder.message;
|
|
||||||
ack = builder.ack;
|
|
||||||
signature = builder.signature;
|
|
||||||
status = builder.status;
|
|
||||||
sent = builder.sent;
|
|
||||||
received = builder.received;
|
|
||||||
labels = builder.labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Plaintext read(Type type, InputStream in) throws IOException {
|
|
||||||
return readWithoutSignature(type, in)
|
|
||||||
.signature(Decode.varBytes(in))
|
|
||||||
.received(UnixTime.now())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException {
|
|
||||||
long version = Decode.varInt(in);
|
|
||||||
return new Builder(type)
|
|
||||||
.addressVersion(version)
|
|
||||||
.stream(Decode.varInt(in))
|
|
||||||
.behaviorBitfield(Decode.int32(in))
|
|
||||||
.publicSigningKey(Decode.bytes(in, 64))
|
|
||||||
.publicEncryptionKey(Decode.bytes(in, 64))
|
|
||||||
.nonceTrialsPerByte(version >= 3 ? Decode.varInt(in) : 0)
|
|
||||||
.extraBytes(version >= 3 ? Decode.varInt(in) : 0)
|
|
||||||
.destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null)
|
|
||||||
.encoding(Decode.varInt(in))
|
|
||||||
.message(Decode.varBytes(in))
|
|
||||||
.ack(type == Type.MSG ? Decode.varBytes(in) : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryVector getInventoryVector() {
|
|
||||||
return inventoryVector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInventoryVector(InventoryVector inventoryVector) {
|
|
||||||
this.inventoryVector = inventoryVector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress getFrom() {
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress getTo() {
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTo(BitmessageAddress to) {
|
|
||||||
if (this.to.getVersion() != 0)
|
|
||||||
throw new RuntimeException("Correct address already set");
|
|
||||||
if (!Arrays.equals(this.to.getRipe(), to.getRipe())) {
|
|
||||||
throw new RuntimeException("RIPEs don't match");
|
|
||||||
}
|
|
||||||
this.to = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Label> getLabels() {
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return from.getStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUnread() {
|
|
||||||
for (Label label : labels) {
|
|
||||||
if (label.getType() == Label.Type.UNREAD) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(OutputStream out, boolean includeSignature) throws IOException {
|
|
||||||
Encode.varInt(from.getVersion(), out);
|
|
||||||
Encode.varInt(from.getStream(), out);
|
|
||||||
Encode.int32(from.getPubkey().getBehaviorBitfield(), out);
|
|
||||||
out.write(from.getPubkey().getSigningKey(), 1, 64);
|
|
||||||
out.write(from.getPubkey().getEncryptionKey(), 1, 64);
|
|
||||||
if (from.getVersion() >= 3) {
|
|
||||||
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
|
|
||||||
Encode.varInt(from.getPubkey().getExtraBytes(), out);
|
|
||||||
}
|
|
||||||
if (type == Type.MSG) {
|
|
||||||
out.write(to.getRipe());
|
|
||||||
}
|
|
||||||
Encode.varInt(encoding, out);
|
|
||||||
Encode.varInt(message.length, out);
|
|
||||||
out.write(message);
|
|
||||||
if (type == Type.MSG) {
|
|
||||||
Encode.varInt(ack.length, out);
|
|
||||||
out.write(ack);
|
|
||||||
}
|
|
||||||
if (includeSignature) {
|
|
||||||
if (signature == null) {
|
|
||||||
Encode.varInt(0, out);
|
|
||||||
} else {
|
|
||||||
Encode.varInt(signature.length, out);
|
|
||||||
out.write(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
write(out, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(long id) {
|
|
||||||
if (this.id != null) throw new IllegalStateException("ID already set");
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getSent() {
|
|
||||||
return sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getReceived() {
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(Status status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubject() {
|
|
||||||
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
|
|
||||||
String firstLine = s.nextLine();
|
|
||||||
if (encoding == 2) {
|
|
||||||
return firstLine.substring("Subject:".length()).trim();
|
|
||||||
} else if (firstLine.length() > 50) {
|
|
||||||
return firstLine.substring(0, 50).trim() + "...";
|
|
||||||
} else {
|
|
||||||
return firstLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getText() {
|
|
||||||
try {
|
|
||||||
String text = new String(message, "UTF-8");
|
|
||||||
if (encoding == 2) {
|
|
||||||
return text.substring(text.indexOf("\nBody:") + 6);
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Plaintext plaintext = (Plaintext) o;
|
|
||||||
return Objects.equals(encoding, plaintext.encoding) &&
|
|
||||||
Objects.equals(from, plaintext.from) &&
|
|
||||||
Arrays.equals(message, plaintext.message) &&
|
|
||||||
Arrays.equals(ack, plaintext.ack) &&
|
|
||||||
Arrays.equals(to.getRipe(), plaintext.to.getRipe()) &&
|
|
||||||
Arrays.equals(signature, plaintext.signature) &&
|
|
||||||
Objects.equals(status, plaintext.status) &&
|
|
||||||
Objects.equals(sent, plaintext.sent) &&
|
|
||||||
Objects.equals(received, plaintext.received) &&
|
|
||||||
Objects.equals(labels, plaintext.labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLabels(Label... labels) {
|
|
||||||
if (labels != null) {
|
|
||||||
Collections.addAll(this.labels, labels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLabels(Collection<Label> labels) {
|
|
||||||
if (labels != null) {
|
|
||||||
this.labels.addAll(labels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInitialHash(byte[] initialHash) {
|
|
||||||
this.initialHash = initialHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getInitialHash() {
|
|
||||||
return initialHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Encoding {
|
|
||||||
IGNORE(0), TRIVIAL(1), SIMPLE(2);
|
|
||||||
|
|
||||||
long code;
|
|
||||||
|
|
||||||
Encoding(long code) {
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Status {
|
|
||||||
DRAFT,
|
|
||||||
// For sent messages
|
|
||||||
PUBKEY_REQUESTED,
|
|
||||||
DOING_PROOF_OF_WORK,
|
|
||||||
SENT,
|
|
||||||
SENT_ACKNOWLEDGED,
|
|
||||||
RECEIVED
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
MSG, BROADCAST
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private Object id;
|
|
||||||
private InventoryVector inventoryVector;
|
|
||||||
private Type type;
|
|
||||||
private BitmessageAddress from;
|
|
||||||
private BitmessageAddress to;
|
|
||||||
private long addressVersion;
|
|
||||||
private long stream;
|
|
||||||
private int behaviorBitfield;
|
|
||||||
private byte[] publicSigningKey;
|
|
||||||
private byte[] publicEncryptionKey;
|
|
||||||
private long nonceTrialsPerByte;
|
|
||||||
private long extraBytes;
|
|
||||||
private byte[] destinationRipe;
|
|
||||||
private long encoding;
|
|
||||||
private byte[] message = new byte[0];
|
|
||||||
private byte[] ack = new byte[0];
|
|
||||||
private byte[] signature;
|
|
||||||
private long sent;
|
|
||||||
private long received;
|
|
||||||
private Status status;
|
|
||||||
private Set<Label> labels = new HashSet<>();
|
|
||||||
|
|
||||||
public Builder(Type type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder id(Object id) {
|
|
||||||
this.id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder IV(InventoryVector iv) {
|
|
||||||
this.inventoryVector = iv;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder from(BitmessageAddress address) {
|
|
||||||
from = address;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder to(BitmessageAddress address) {
|
|
||||||
if (type != Type.MSG && to != null)
|
|
||||||
throw new IllegalArgumentException("recipient address only allowed for msg");
|
|
||||||
to = address;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder addressVersion(long addressVersion) {
|
|
||||||
this.addressVersion = addressVersion;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder stream(long stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder behaviorBitfield(int behaviorBitfield) {
|
|
||||||
this.behaviorBitfield = behaviorBitfield;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder publicSigningKey(byte[] publicSigningKey) {
|
|
||||||
this.publicSigningKey = publicSigningKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
|
||||||
this.publicEncryptionKey = publicEncryptionKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
|
|
||||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder extraBytes(long extraBytes) {
|
|
||||||
this.extraBytes = extraBytes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder destinationRipe(byte[] ripe) {
|
|
||||||
if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg");
|
|
||||||
this.destinationRipe = ripe;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder encoding(Encoding encoding) {
|
|
||||||
this.encoding = encoding.getCode();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder encoding(long encoding) {
|
|
||||||
this.encoding = encoding;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder message(String subject, String message) {
|
|
||||||
try {
|
|
||||||
this.encoding = Encoding.SIMPLE.getCode();
|
|
||||||
this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder message(byte[] message) {
|
|
||||||
this.message = message;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ack(byte[] ack) {
|
|
||||||
if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg");
|
|
||||||
this.ack = ack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder signature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder sent(long sent) {
|
|
||||||
this.sent = sent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder received(long received) {
|
|
||||||
this.received = received;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder status(Status status) {
|
|
||||||
this.status = status;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder labels(Collection<Label> labels) {
|
|
||||||
this.labels.addAll(labels);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plaintext build() {
|
|
||||||
if (from == null) {
|
|
||||||
from = new BitmessageAddress(Factory.createPubkey(
|
|
||||||
addressVersion,
|
|
||||||
stream,
|
|
||||||
publicSigningKey,
|
|
||||||
publicEncryptionKey,
|
|
||||||
nonceTrialsPerByte,
|
|
||||||
extraBytes,
|
|
||||||
behaviorBitfield
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (to == null && type != Type.BROADCAST) {
|
|
||||||
to = new BitmessageAddress(0, 0, destinationRipe);
|
|
||||||
}
|
|
||||||
return new Plaintext(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,203 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
|
||||||
*/
|
|
||||||
public class Version implements MessagePayload {
|
|
||||||
/**
|
|
||||||
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
|
|
||||||
* version is lower but continue with the connection if it is higher.
|
|
||||||
*/
|
|
||||||
private final int version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bitfield of features to be enabled for this connection
|
|
||||||
*/
|
|
||||||
private final long services;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* standard UNIX timestamp in seconds
|
|
||||||
*/
|
|
||||||
private final long timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The network address of the node receiving this message (not including the time or stream number)
|
|
||||||
*/
|
|
||||||
private final NetworkAddress addrRecv;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The network address of the node emitting this message (not including the time or stream number and the ip itself
|
|
||||||
* is ignored by the receiver)
|
|
||||||
*/
|
|
||||||
private final NetworkAddress addrFrom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Random nonce used to detect connections to self.
|
|
||||||
*/
|
|
||||||
private final long nonce;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes.
|
|
||||||
*/
|
|
||||||
private final String userAgent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000
|
|
||||||
* stream numbers.
|
|
||||||
*/
|
|
||||||
private final long[] streams;
|
|
||||||
|
|
||||||
private Version(Builder builder) {
|
|
||||||
version = builder.version;
|
|
||||||
services = builder.services;
|
|
||||||
timestamp = builder.timestamp;
|
|
||||||
addrRecv = builder.addrRecv;
|
|
||||||
addrFrom = builder.addrFrom;
|
|
||||||
nonce = builder.nonce;
|
|
||||||
userAgent = builder.userAgent;
|
|
||||||
streams = builder.streamNumbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getServices() {
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkAddress getAddrRecv() {
|
|
||||||
return addrRecv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkAddress getAddrFrom() {
|
|
||||||
return addrFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonce() {
|
|
||||||
return nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserAgent() {
|
|
||||||
return userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long[] getStreams() {
|
|
||||||
return streams;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
Encode.int32(version, stream);
|
|
||||||
Encode.int64(services, stream);
|
|
||||||
Encode.int64(timestamp, stream);
|
|
||||||
addrRecv.write(stream, true);
|
|
||||||
addrFrom.write(stream, true);
|
|
||||||
Encode.int64(nonce, stream);
|
|
||||||
Encode.varString(userAgent, stream);
|
|
||||||
Encode.varIntList(streams, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private int version;
|
|
||||||
private long services;
|
|
||||||
private long timestamp;
|
|
||||||
private NetworkAddress addrRecv;
|
|
||||||
private NetworkAddress addrFrom;
|
|
||||||
private long nonce;
|
|
||||||
private String userAgent;
|
|
||||||
private long[] streamNumbers;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder defaults() {
|
|
||||||
version = BitmessageContext.CURRENT_VERSION;
|
|
||||||
services = 1;
|
|
||||||
timestamp = UnixTime.now();
|
|
||||||
nonce = new Random().nextInt();
|
|
||||||
userAgent = "/Jabit:0.0.1/";
|
|
||||||
streamNumbers = new long[]{1};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder version(int version) {
|
|
||||||
this.version = version;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder services(long services) {
|
|
||||||
this.services = services;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder timestamp(long timestamp) {
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addrRecv(NetworkAddress addrRecv) {
|
|
||||||
this.addrRecv = addrRecv;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addrFrom(NetworkAddress addrFrom) {
|
|
||||||
this.addrFrom = addrFrom;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nonce(long nonce) {
|
|
||||||
this.nonce = nonce;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder userAgent(String userAgent) {
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder streams(long... streamNumbers) {
|
|
||||||
this.streamNumbers = streamNumbers;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version build() {
|
|
||||||
return new Version(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
|
||||||
* Broadcasts are version 4 or 5.
|
|
||||||
*/
|
|
||||||
public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder {
|
|
||||||
protected final long stream;
|
|
||||||
protected CryptoBox encrypted;
|
|
||||||
protected Plaintext plaintext;
|
|
||||||
|
|
||||||
protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
|
|
||||||
super(version);
|
|
||||||
this.stream = stream;
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
this.plaintext = plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getVersion(BitmessageAddress address) {
|
|
||||||
return address.getVersion() < 4 ? 4 : 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return plaintext.getSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
plaintext.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plaintext getPlaintext() {
|
|
||||||
return plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encrypt(byte[] publicKey) throws IOException {
|
|
||||||
this.encrypted = new CryptoBox(plaintext, publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void encrypt() throws IOException {
|
|
||||||
encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
|
||||||
plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException {
|
|
||||||
decrypt(address.getPublicDecryptionKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDecrypted() {
|
|
||||||
return plaintext != null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,196 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.*;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
|
|
||||||
public class CryptoBox implements Streamable {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class);
|
|
||||||
|
|
||||||
private final byte[] initializationVector;
|
|
||||||
private final int curveType;
|
|
||||||
private final byte[] R;
|
|
||||||
private final byte[] mac;
|
|
||||||
private byte[] encrypted;
|
|
||||||
|
|
||||||
private long addressVersion;
|
|
||||||
|
|
||||||
|
|
||||||
public CryptoBox(Streamable data, byte[] K) throws IOException {
|
|
||||||
this(Encode.bytes(data), K);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CryptoBox(byte[] data, byte[] K) throws IOException {
|
|
||||||
curveType = 0x02CA;
|
|
||||||
|
|
||||||
// 1. The destination public key is called K.
|
|
||||||
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
|
|
||||||
initializationVector = security().randomBytes(16);
|
|
||||||
|
|
||||||
// 3. Generate a new random EC key pair with private key called r and public key called R.
|
|
||||||
byte[] r = security().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
R = security().createPublicKey(r);
|
|
||||||
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
|
|
||||||
byte[] P = security().multiply(K, r);
|
|
||||||
byte[] X = Points.getX(P);
|
|
||||||
// 5. Use the X component of public key P and calculate the SHA512 hash H.
|
|
||||||
byte[] H = security().sha512(X);
|
|
||||||
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
|
||||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
|
||||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
|
||||||
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
|
|
||||||
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
|
|
||||||
encrypted = security().crypt(true, data, key_e, initializationVector);
|
|
||||||
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
|
|
||||||
mac = calculateMac(key_m);
|
|
||||||
|
|
||||||
// The resulting data is: IV + R + cipher text + MAC
|
|
||||||
}
|
|
||||||
|
|
||||||
private CryptoBox(Builder builder) {
|
|
||||||
initializationVector = builder.initializationVector;
|
|
||||||
curveType = builder.curveType;
|
|
||||||
R = security().createPoint(builder.xComponent, builder.yComponent);
|
|
||||||
encrypted = builder.encrypted;
|
|
||||||
mac = builder.mac;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CryptoBox read(InputStream stream, int length) throws IOException {
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
return new Builder()
|
|
||||||
.IV(Decode.bytes(stream, 16, counter))
|
|
||||||
.curveType(Decode.uint16(stream, counter))
|
|
||||||
.X(Decode.shortVarBytes(stream, counter))
|
|
||||||
.Y(Decode.shortVarBytes(stream, counter))
|
|
||||||
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
|
|
||||||
.MAC(Decode.bytes(stream, 32))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param k a private key, typically should be 32 bytes long
|
|
||||||
* @return an InputStream yielding the decrypted data
|
|
||||||
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
|
|
||||||
* @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a>
|
|
||||||
*/
|
|
||||||
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
|
|
||||||
// 1. The private key used to decrypt is called k.
|
|
||||||
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
|
|
||||||
byte[] P = security().multiply(R, k);
|
|
||||||
// 3. Use the X component of public key P and calculate the SHA512 hash H.
|
|
||||||
byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33));
|
|
||||||
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
|
||||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
|
||||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
|
||||||
|
|
||||||
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
|
|
||||||
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
|
|
||||||
if (!Arrays.equals(mac, calculateMac(key_m))) {
|
|
||||||
throw new DecryptionFailedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
|
|
||||||
// and the cipher text as payload. The output is the padded input text.
|
|
||||||
return new ByteArrayInputStream(security().crypt(false, encrypted, key_e, initializationVector));
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] calculateMac(byte[] key_m) {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream macData = new ByteArrayOutputStream();
|
|
||||||
writeWithoutMAC(macData);
|
|
||||||
return security().mac(key_m, macData.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeWithoutMAC(OutputStream out) throws IOException {
|
|
||||||
out.write(initializationVector);
|
|
||||||
Encode.int16(curveType, out);
|
|
||||||
writeCoordinateComponent(out, Points.getX(R));
|
|
||||||
writeCoordinateComponent(out, Points.getY(R));
|
|
||||||
out.write(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
|
|
||||||
int offset = Bytes.numberOfLeadingZeros(x);
|
|
||||||
int length = x.length - offset;
|
|
||||||
Encode.int16(length, out);
|
|
||||||
out.write(x, offset, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
writeWithoutMAC(stream);
|
|
||||||
stream.write(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private byte[] initializationVector;
|
|
||||||
private int curveType;
|
|
||||||
private byte[] xComponent;
|
|
||||||
private byte[] yComponent;
|
|
||||||
private byte[] encrypted;
|
|
||||||
private byte[] mac;
|
|
||||||
|
|
||||||
public Builder IV(byte[] initializationVector) {
|
|
||||||
this.initializationVector = initializationVector;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder curveType(int curveType) {
|
|
||||||
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType);
|
|
||||||
this.curveType = curveType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder X(byte[] xComponent) {
|
|
||||||
this.xComponent = xComponent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder Y(byte[] yComponent) {
|
|
||||||
this.yComponent = yComponent;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder encrypted(byte[] encrypted) {
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder MAC(byte[] mac) {
|
|
||||||
this.mac = mac;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CryptoBox build() {
|
|
||||||
return new CryptoBox(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
|
|
||||||
* have to know what it is.
|
|
||||||
*/
|
|
||||||
public class GenericPayload extends ObjectPayload {
|
|
||||||
private long stream;
|
|
||||||
private byte[] data;
|
|
||||||
|
|
||||||
public GenericPayload(long version, long stream, byte[] data) {
|
|
||||||
super(version);
|
|
||||||
this.stream = stream;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GenericPayload read(long version, InputStream is, long stream, int length) throws IOException {
|
|
||||||
return new GenericPayload(version, stream, Decode.bytes(is, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
GenericPayload that = (GenericPayload) o;
|
|
||||||
|
|
||||||
if (stream != that.stream) return false;
|
|
||||||
return Arrays.equals(data, that.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = (int) (stream ^ (stream >>> 32));
|
|
||||||
result = 31 * result + Arrays.hashCode(data);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for a public key.
|
|
||||||
*/
|
|
||||||
public class GetPubkey extends ObjectPayload {
|
|
||||||
private long stream;
|
|
||||||
private byte[] ripeTag;
|
|
||||||
|
|
||||||
public GetPubkey(BitmessageAddress address) {
|
|
||||||
super(address.getVersion());
|
|
||||||
this.stream = address.getStream();
|
|
||||||
if (address.getVersion() < 4)
|
|
||||||
this.ripeTag = address.getRipe();
|
|
||||||
else
|
|
||||||
this.ripeTag = address.getTag();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GetPubkey(long version, long stream, byte[] ripeOrTag) {
|
|
||||||
super(version);
|
|
||||||
this.stream = stream;
|
|
||||||
this.ripeTag = ripeOrTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException {
|
|
||||||
return new GetPubkey(version, stream, Decode.bytes(is, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return an array of bytes that represent either the ripe, or the tag of an address, depending on the
|
|
||||||
* address version.
|
|
||||||
*/
|
|
||||||
public byte[] getRipeTag() {
|
|
||||||
return ripeTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.GET_PUBKEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(ripeTag);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.PlaintextHolder;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for person-to-person messages.
|
|
||||||
*/
|
|
||||||
public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
|
|
||||||
private long stream;
|
|
||||||
private CryptoBox encrypted;
|
|
||||||
private Plaintext plaintext;
|
|
||||||
|
|
||||||
private Msg(long stream, CryptoBox encrypted) {
|
|
||||||
super(1);
|
|
||||||
this.stream = stream;
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Msg(Plaintext plaintext) {
|
|
||||||
super(1);
|
|
||||||
this.stream = plaintext.getStream();
|
|
||||||
this.plaintext = plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Msg read(InputStream in, long stream, int length) throws IOException {
|
|
||||||
return new Msg(stream, CryptoBox.read(in, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plaintext getPlaintext() {
|
|
||||||
return plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.MSG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
plaintext.write(out, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return plaintext.getSignature();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
plaintext.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encrypt(byte[] publicKey) throws IOException {
|
|
||||||
this.encrypted = new CryptoBox(plaintext, publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
|
||||||
plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDecrypted() {
|
|
||||||
return plaintext != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
|
|
||||||
encrypted.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The payload of an 'object' command. This is shared by the network.
|
|
||||||
*/
|
|
||||||
public abstract class ObjectPayload implements Streamable {
|
|
||||||
private final long version;
|
|
||||||
|
|
||||||
protected ObjectPayload(long version) {
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract ObjectType getType();
|
|
||||||
|
|
||||||
public abstract long getStream();
|
|
||||||
|
|
||||||
public long getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
|
||||||
* appended with the data described in this table down to the extra_bytes. Therefore, this must
|
|
||||||
* be checked and set in the {@link ObjectMessage} object.
|
|
||||||
*/
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
|
||||||
*/
|
|
||||||
public abstract class Pubkey extends ObjectPayload {
|
|
||||||
public final static long LATEST_VERSION = 4;
|
|
||||||
|
|
||||||
protected Pubkey(long version) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
|
|
||||||
return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract byte[] getSigningKey();
|
|
||||||
|
|
||||||
public abstract byte[] getEncryptionKey();
|
|
||||||
|
|
||||||
public abstract int getBehaviorBitfield();
|
|
||||||
|
|
||||||
public byte[] getRipe() {
|
|
||||||
return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonceTrialsPerByte() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtraBytes() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
|
||||||
write(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] add0x04(byte[] key) {
|
|
||||||
if (key.length == 65) return key;
|
|
||||||
byte[] result = new byte[65];
|
|
||||||
result[0] = 4;
|
|
||||||
System.arraycopy(key, 0, result, 1, 64);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bits 0 through 29 are yet undefined
|
|
||||||
*/
|
|
||||||
public enum Feature {
|
|
||||||
/**
|
|
||||||
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
|
|
||||||
* messages bound for them.
|
|
||||||
*/
|
|
||||||
INCLUDE_DESTINATION(1 << 30),
|
|
||||||
/**
|
|
||||||
* If true, the receiving node does send acknowledgements (rather than dropping them).
|
|
||||||
*/
|
|
||||||
DOES_ACK(1 << 31);
|
|
||||||
|
|
||||||
private int bit;
|
|
||||||
|
|
||||||
Feature(int bit) {
|
|
||||||
this.bit = bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int bitfield(Feature... features) {
|
|
||||||
int bits = 0;
|
|
||||||
for (Feature feature : features) {
|
|
||||||
bits |= feature.bit;
|
|
||||||
}
|
|
||||||
return bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Feature[] features(int bitfield) {
|
|
||||||
ArrayList<Feature> features = new ArrayList<>(Feature.values().length);
|
|
||||||
for (Feature feature : Feature.values()) {
|
|
||||||
if ((bitfield & feature.bit) != 0) {
|
|
||||||
features.add(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return features.toArray(new Feature[features.size()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version 2 public key.
|
|
||||||
*/
|
|
||||||
public class V2Pubkey extends Pubkey {
|
|
||||||
protected long stream;
|
|
||||||
protected int behaviorBitfield;
|
|
||||||
protected byte[] publicSigningKey; // 64 Bytes
|
|
||||||
protected byte[] publicEncryptionKey; // 64 Bytes
|
|
||||||
|
|
||||||
protected V2Pubkey(long version) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
private V2Pubkey(long version, Builder builder) {
|
|
||||||
super(version);
|
|
||||||
stream = builder.streamNumber;
|
|
||||||
behaviorBitfield = builder.behaviorBitfield;
|
|
||||||
publicSigningKey = add0x04(builder.publicSigningKey);
|
|
||||||
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V2Pubkey read(InputStream is, long stream) throws IOException {
|
|
||||||
return new V2Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.behaviorBitfield((int) Decode.uint32(is))
|
|
||||||
.publicSigningKey(Decode.bytes(is, 64))
|
|
||||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getVersion() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.PUBKEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSigningKey() {
|
|
||||||
return publicSigningKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncryptionKey() {
|
|
||||||
return publicEncryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBehaviorBitfield() {
|
|
||||||
return behaviorBitfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream os) throws IOException {
|
|
||||||
Encode.int32(behaviorBitfield, os);
|
|
||||||
os.write(publicSigningKey, 1, 64);
|
|
||||||
os.write(publicEncryptionKey, 1, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private long streamNumber;
|
|
||||||
private int behaviorBitfield;
|
|
||||||
private byte[] publicSigningKey;
|
|
||||||
private byte[] publicEncryptionKey;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(long streamNumber) {
|
|
||||||
this.streamNumber = streamNumber;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder behaviorBitfield(int behaviorBitfield) {
|
|
||||||
this.behaviorBitfield = behaviorBitfield;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicSigningKey(byte[] publicSigningKey) {
|
|
||||||
this.publicSigningKey = publicSigningKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
|
||||||
this.publicEncryptionKey = publicEncryptionKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V2Pubkey build() {
|
|
||||||
return new V2Pubkey(2, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version 3 public key.
|
|
||||||
*/
|
|
||||||
public class V3Pubkey extends V2Pubkey {
|
|
||||||
long nonceTrialsPerByte;
|
|
||||||
long extraBytes;
|
|
||||||
byte[] signature;
|
|
||||||
|
|
||||||
protected V3Pubkey(long version, Builder builder) {
|
|
||||||
super(version);
|
|
||||||
stream = builder.streamNumber;
|
|
||||||
behaviorBitfield = builder.behaviorBitfield;
|
|
||||||
publicSigningKey = add0x04(builder.publicSigningKey);
|
|
||||||
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
|
|
||||||
nonceTrialsPerByte = builder.nonceTrialsPerByte;
|
|
||||||
extraBytes = builder.extraBytes;
|
|
||||||
signature = builder.signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V3Pubkey read(InputStream is, long stream) throws IOException {
|
|
||||||
return new V3Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.behaviorBitfield(Decode.int32(is))
|
|
||||||
.publicSigningKey(Decode.bytes(is, 64))
|
|
||||||
.publicEncryptionKey(Decode.bytes(is, 64))
|
|
||||||
.nonceTrialsPerByte(Decode.varInt(is))
|
|
||||||
.extraBytes(Decode.varInt(is))
|
|
||||||
.signature(Decode.varBytes(is))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
writeBytesToSign(out);
|
|
||||||
Encode.varInt(signature.length, out);
|
|
||||||
out.write(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getVersion() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonceTrialsPerByte() {
|
|
||||||
return nonceTrialsPerByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtraBytes() {
|
|
||||||
return extraBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
super.write(out);
|
|
||||||
Encode.varInt(nonceTrialsPerByte, out);
|
|
||||||
Encode.varInt(extraBytes, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
V3Pubkey pubkey = (V3Pubkey) o;
|
|
||||||
return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) &&
|
|
||||||
Objects.equals(extraBytes, pubkey.extraBytes) &&
|
|
||||||
stream == pubkey.stream &&
|
|
||||||
behaviorBitfield == pubkey.behaviorBitfield &&
|
|
||||||
Arrays.equals(publicSigningKey, pubkey.publicSigningKey) &&
|
|
||||||
Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(nonceTrialsPerByte, extraBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private long streamNumber;
|
|
||||||
private int behaviorBitfield;
|
|
||||||
private byte[] publicSigningKey;
|
|
||||||
private byte[] publicEncryptionKey;
|
|
||||||
private long nonceTrialsPerByte;
|
|
||||||
private long extraBytes;
|
|
||||||
private byte[] signature = new byte[0];
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(long streamNumber) {
|
|
||||||
this.streamNumber = streamNumber;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder behaviorBitfield(int behaviorBitfield) {
|
|
||||||
this.behaviorBitfield = behaviorBitfield;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicSigningKey(byte[] publicSigningKey) {
|
|
||||||
this.publicSigningKey = publicSigningKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
|
|
||||||
this.publicEncryptionKey = publicEncryptionKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
|
|
||||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder extraBytes(long extraBytes) {
|
|
||||||
this.extraBytes = extraBytes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder signature(byte[] signature) {
|
|
||||||
this.signature = signature;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V3Pubkey build() {
|
|
||||||
return new V3Pubkey(3, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
|
||||||
* Broadcasts are version 4 or 5.
|
|
||||||
*/
|
|
||||||
public class V4Broadcast extends Broadcast {
|
|
||||||
protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
|
|
||||||
super(version, stream, encrypted, plaintext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
|
|
||||||
super(4, senderAddress.getStream(), null, plaintext);
|
|
||||||
if (senderAddress.getVersion() >= 4)
|
|
||||||
throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V4Broadcast read(InputStream in, long stream, int length) throws IOException {
|
|
||||||
return new V4Broadcast(4, stream, CryptoBox.read(in, length), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.BROADCAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
plaintext.write(out, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
encrypted.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Encrypted;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
|
|
||||||
* done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and
|
|
||||||
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
|
|
||||||
* to create messages to be used in spam or in flooding attacks.
|
|
||||||
*/
|
|
||||||
public class V4Pubkey extends Pubkey implements Encrypted {
|
|
||||||
private long stream;
|
|
||||||
private byte[] tag;
|
|
||||||
private CryptoBox encrypted;
|
|
||||||
private V3Pubkey decrypted;
|
|
||||||
|
|
||||||
private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) {
|
|
||||||
super(4);
|
|
||||||
this.stream = stream;
|
|
||||||
this.tag = tag;
|
|
||||||
this.encrypted = encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V4Pubkey(V3Pubkey decrypted) {
|
|
||||||
super(4);
|
|
||||||
this.decrypted = decrypted;
|
|
||||||
this.stream = decrypted.stream;
|
|
||||||
this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException {
|
|
||||||
if (encrypted)
|
|
||||||
return new V4Pubkey(stream,
|
|
||||||
Decode.bytes(in, 32),
|
|
||||||
CryptoBox.read(in, length - 32));
|
|
||||||
else
|
|
||||||
return new V4Pubkey(V3Pubkey.read(in, stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encrypt(byte[] publicKey) throws IOException {
|
|
||||||
if (getSignature() == null) throw new IllegalStateException("Pubkey must be signed before encryption.");
|
|
||||||
this.encrypted = new CryptoBox(decrypted, publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
|
|
||||||
decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDecrypted() {
|
|
||||||
return decrypted != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(tag);
|
|
||||||
encrypted.write(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
|
||||||
decrypted.write(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
out.write(tag);
|
|
||||||
decrypted.writeBytesToSign(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getVersion() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectType getType() {
|
|
||||||
return ObjectType.PUBKEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSigningKey() {
|
|
||||||
return decrypted.getSigningKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncryptionKey() {
|
|
||||||
return decrypted.getEncryptionKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getBehaviorBitfield() {
|
|
||||||
return decrypted.getBehaviorBitfield();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSignature() {
|
|
||||||
if (decrypted != null)
|
|
||||||
return decrypted.getSignature();
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSignature(byte[] signature) {
|
|
||||||
decrypted.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSigned() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonceTrialsPerByte() {
|
|
||||||
return decrypted.getNonceTrialsPerByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtraBytes() {
|
|
||||||
return decrypted.getExtraBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
V4Pubkey v4Pubkey = (V4Pubkey) o;
|
|
||||||
|
|
||||||
if (stream != v4Pubkey.stream) return false;
|
|
||||||
if (!Arrays.equals(tag, v4Pubkey.tag)) return false;
|
|
||||||
return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = (int) (stream ^ (stream >>> 32));
|
|
||||||
result = 31 * result + Arrays.hashCode(tag);
|
|
||||||
result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
|
||||||
*/
|
|
||||||
public class V5Broadcast extends V4Broadcast {
|
|
||||||
private byte[] tag;
|
|
||||||
|
|
||||||
private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) {
|
|
||||||
super(5, stream, encrypted, null);
|
|
||||||
this.tag = tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
|
|
||||||
super(5, senderAddress.getStream(), null, plaintext);
|
|
||||||
if (senderAddress.getVersion() < 4)
|
|
||||||
throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion());
|
|
||||||
this.tag = senderAddress.getTag();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static V5Broadcast read(InputStream is, long stream, int length) throws IOException {
|
|
||||||
return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getTag() {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeBytesToSign(OutputStream out) throws IOException {
|
|
||||||
out.write(tag);
|
|
||||||
super.writeBytesToSign(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
out.write(tag);
|
|
||||||
super.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.utils.Strings;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class InventoryVector implements Streamable, Serializable {
|
|
||||||
/**
|
|
||||||
* Hash of the object
|
|
||||||
*/
|
|
||||||
private final byte[] hash;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (!(o instanceof InventoryVector)) return false;
|
|
||||||
|
|
||||||
InventoryVector that = (InventoryVector) o;
|
|
||||||
|
|
||||||
return Arrays.equals(hash, that.hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hash != null ? Arrays.hashCode(hash) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getHash() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryVector(byte[] hash) {
|
|
||||||
this.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Strings.hex(hash).toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class Label implements Serializable {
|
|
||||||
private Object id;
|
|
||||||
private String label;
|
|
||||||
private Type type;
|
|
||||||
private int color;
|
|
||||||
|
|
||||||
public Label(String label, Type type, int color) {
|
|
||||||
this.label = label;
|
|
||||||
this.type = type;
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return RGBA representation for the color.
|
|
||||||
*/
|
|
||||||
public int getColor() {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param color RGBA representation for the color.
|
|
||||||
*/
|
|
||||||
public void setColor(int color) {
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Object id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Label label1 = (Label) o;
|
|
||||||
return Objects.equals(label, label1.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
INBOX,
|
|
||||||
BROADCAST,
|
|
||||||
DRAFT,
|
|
||||||
OUTBOX,
|
|
||||||
SENT,
|
|
||||||
UNREAD,
|
|
||||||
TRASH
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A node's address. It's written in IPv6 format.
|
|
||||||
*/
|
|
||||||
public class NetworkAddress implements Streamable {
|
|
||||||
private long time;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream number for this node
|
|
||||||
*/
|
|
||||||
private long stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* same service(s) listed in version
|
|
||||||
*/
|
|
||||||
private long services;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address
|
|
||||||
* (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address).
|
|
||||||
*/
|
|
||||||
private byte[] ipv6;
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
private NetworkAddress(Builder builder) {
|
|
||||||
time = builder.time;
|
|
||||||
stream = builder.stream;
|
|
||||||
services = builder.services;
|
|
||||||
ipv6 = builder.ipv6;
|
|
||||||
port = builder.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getIPv6() {
|
|
||||||
return ipv6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getServices() {
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTime(long time) {
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InetAddress toInetAddress() {
|
|
||||||
try {
|
|
||||||
return InetAddress.getByAddress(ipv6);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
NetworkAddress that = (NetworkAddress) o;
|
|
||||||
|
|
||||||
return port == that.port && Arrays.equals(ipv6, that.ipv6);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = ipv6 != null ? Arrays.hashCode(ipv6) : 0;
|
|
||||||
result = 31 * result + port;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "[" + toInetAddress() + "]:" + port;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
write(stream, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(OutputStream stream, boolean light) throws IOException {
|
|
||||||
if (!light) {
|
|
||||||
Encode.int64(time, stream);
|
|
||||||
Encode.int32(this.stream, stream);
|
|
||||||
}
|
|
||||||
Encode.int64(services, stream);
|
|
||||||
stream.write(ipv6);
|
|
||||||
Encode.int16(port, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private long time;
|
|
||||||
private long stream;
|
|
||||||
private long services = 1;
|
|
||||||
private byte[] ipv6;
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
public Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder time(final long time) {
|
|
||||||
this.time = time;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder stream(final long stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder services(final long services) {
|
|
||||||
this.services = services;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ip(InetAddress inetAddress) {
|
|
||||||
byte[] addr = inetAddress.getAddress();
|
|
||||||
if (addr.length == 16) {
|
|
||||||
this.ipv6 = addr;
|
|
||||||
} else if (addr.length == 4) {
|
|
||||||
this.ipv6 = new byte[16];
|
|
||||||
this.ipv6[10] = (byte) 0xff;
|
|
||||||
this.ipv6[11] = (byte) 0xff;
|
|
||||||
System.arraycopy(addr, 0, this.ipv6, 12, 4);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Weird address " + inetAddress);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ipv6(byte[] ipv6) {
|
|
||||||
this.ipv6 = ipv6;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ipv6(int p00, int p01, int p02, int p03,
|
|
||||||
int p04, int p05, int p06, int p07,
|
|
||||||
int p08, int p09, int p10, int p11,
|
|
||||||
int p12, int p13, int p14, int p15) {
|
|
||||||
this.ipv6 = new byte[]{
|
|
||||||
(byte) p00, (byte) p01, (byte) p02, (byte) p03,
|
|
||||||
(byte) p04, (byte) p05, (byte) p06, (byte) p07,
|
|
||||||
(byte) p08, (byte) p09, (byte) p10, (byte) p11,
|
|
||||||
(byte) p12, (byte) p13, (byte) p14, (byte) p15
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ipv4(int p00, int p01, int p02, int p03) {
|
|
||||||
this.ipv6 = new byte[]{
|
|
||||||
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
|
|
||||||
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
|
|
||||||
(byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff,
|
|
||||||
(byte) p00, (byte) p01, (byte) p02, (byte) p03
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder port(final int port) {
|
|
||||||
this.port = port;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkAddress build() {
|
|
||||||
if (time == 0) {
|
|
||||||
time = UnixTime.now();
|
|
||||||
}
|
|
||||||
return new NetworkAddress(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.valueobject;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
|
|
||||||
* {@link Pubkey} object.
|
|
||||||
*/
|
|
||||||
public class PrivateKey implements Streamable {
|
|
||||||
public static final int PRIVATE_KEY_SIZE = 32;
|
|
||||||
private final byte[] privateSigningKey;
|
|
||||||
private final byte[] privateEncryptionKey;
|
|
||||||
|
|
||||||
private final Pubkey pubkey;
|
|
||||||
|
|
||||||
public PrivateKey(boolean shorter, long stream, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
byte[] privSK;
|
|
||||||
byte[] pubSK;
|
|
||||||
byte[] privEK;
|
|
||||||
byte[] pubEK;
|
|
||||||
byte[] ripe;
|
|
||||||
do {
|
|
||||||
privSK = security().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
privEK = security().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
pubSK = security().createPublicKey(privSK);
|
|
||||||
pubEK = security().createPublicKey(privEK);
|
|
||||||
ripe = Pubkey.getRipe(pubSK, pubEK);
|
|
||||||
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
|
|
||||||
this.privateSigningKey = privSK;
|
|
||||||
this.privateEncryptionKey = privEK;
|
|
||||||
this.pubkey = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
|
|
||||||
nonceTrialsPerByte, extraBytes, features);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) {
|
|
||||||
this.privateSigningKey = privateSigningKey;
|
|
||||||
this.privateEncryptionKey = privateEncryptionKey;
|
|
||||||
this.pubkey = pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey(long version, long stream, String passphrase, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
try {
|
|
||||||
// FIXME: this is most definitely wrong
|
|
||||||
this.privateSigningKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
|
|
||||||
this.privateEncryptionKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
|
|
||||||
this.pubkey = security().createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
|
|
||||||
nonceTrialsPerByte, extraBytes, features);
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrivateKey read(InputStream is) throws IOException {
|
|
||||||
int version = (int) Decode.varInt(is);
|
|
||||||
long stream = Decode.varInt(is);
|
|
||||||
int len = (int) Decode.varInt(is);
|
|
||||||
Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false);
|
|
||||||
len = (int) Decode.varInt(is);
|
|
||||||
byte[] signingKey = Decode.bytes(is, len);
|
|
||||||
len = (int) Decode.varInt(is);
|
|
||||||
byte[] encryptionKey = Decode.bytes(is, len);
|
|
||||||
return new PrivateKey(signingKey, encryptionKey, pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPrivateSigningKey() {
|
|
||||||
return privateSigningKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPrivateEncryptionKey() {
|
|
||||||
return privateEncryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pubkey getPubkey() {
|
|
||||||
return pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
Encode.varInt(pubkey.getVersion(), out);
|
|
||||||
Encode.varInt(pubkey.getStream(), out);
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
pubkey.writeUnencrypted(baos);
|
|
||||||
Encode.varInt(baos.size(), out);
|
|
||||||
out.write(baos.toByteArray());
|
|
||||||
Encode.varInt(privateSigningKey.length, out);
|
|
||||||
out.write(privateSigningKey);
|
|
||||||
Encode.varInt(privateEncryptionKey.length, out);
|
|
||||||
out.write(privateEncryptionKey);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Strings;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class InsufficientProofOfWorkException extends IOException {
|
|
||||||
public InsufficientProofOfWorkException(byte[] target, byte[] hash) {
|
|
||||||
super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,206 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.factory;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.NetworkMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.*;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.NodeException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
|
|
||||||
*/
|
|
||||||
public class Factory {
|
|
||||||
public static final Logger LOG = LoggerFactory.getLogger(Factory.class);
|
|
||||||
|
|
||||||
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException {
|
|
||||||
try {
|
|
||||||
return V3MessageFactory.read(stream);
|
|
||||||
} catch (SocketTimeoutException | NodeException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (SocketException e) {
|
|
||||||
throw new NodeException(e.getMessage(), e);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectMessage getObjectMessage(int version, InputStream stream, int length) {
|
|
||||||
try {
|
|
||||||
return V3MessageFactory.readObject(stream, length);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
|
|
||||||
Pubkey.Feature.bitfield(features));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) {
|
|
||||||
if (publicSigningKey.length != 64 && publicSigningKey.length != 65)
|
|
||||||
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
|
|
||||||
+ publicSigningKey.length + " bytes long.");
|
|
||||||
if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65)
|
|
||||||
throw new IllegalArgumentException("64 bytes encryption key expected, but it was "
|
|
||||||
+ publicEncryptionKey.length + " bytes long.");
|
|
||||||
|
|
||||||
switch ((int) version) {
|
|
||||||
case 2:
|
|
||||||
return new V2Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.publicSigningKey(publicSigningKey)
|
|
||||||
.publicEncryptionKey(publicEncryptionKey)
|
|
||||||
.behaviorBitfield(behaviourBitfield)
|
|
||||||
.build();
|
|
||||||
case 3:
|
|
||||||
return new V3Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.publicSigningKey(publicSigningKey)
|
|
||||||
.publicEncryptionKey(publicEncryptionKey)
|
|
||||||
.behaviorBitfield(behaviourBitfield)
|
|
||||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
|
||||||
.extraBytes(extraBytes)
|
|
||||||
.build();
|
|
||||||
case 4:
|
|
||||||
return new V4Pubkey(
|
|
||||||
new V3Pubkey.Builder()
|
|
||||||
.stream(stream)
|
|
||||||
.publicSigningKey(publicSigningKey)
|
|
||||||
.publicEncryptionKey(publicEncryptionKey)
|
|
||||||
.behaviorBitfield(behaviourBitfield)
|
|
||||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
|
||||||
.extraBytes(extraBytes)
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unexpected pubkey version " + version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BitmessageAddress createIdentityFromPrivateKey(String address,
|
|
||||||
byte[] privateSigningKey, byte[] privateEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes,
|
|
||||||
int behaviourBitfield) {
|
|
||||||
BitmessageAddress temp = new BitmessageAddress(address);
|
|
||||||
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
|
|
||||||
createPubkey(temp.getVersion(), temp.getStream(),
|
|
||||||
security().createPublicKey(privateSigningKey),
|
|
||||||
security().createPublicKey(privateEncryptionKey),
|
|
||||||
nonceTrialsPerByte, extraBytes, behaviourBitfield));
|
|
||||||
BitmessageAddress result = new BitmessageAddress(privateKey);
|
|
||||||
if (!result.getAddress().equals(address)) {
|
|
||||||
throw new IllegalArgumentException("Address not matching private key. Address: " + address
|
|
||||||
+ "; Address derived from private key: " + result.getAddress());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BitmessageAddress generatePrivateAddress(boolean shorter,
|
|
||||||
long stream,
|
|
||||||
Pubkey.Feature... features) {
|
|
||||||
return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features));
|
|
||||||
}
|
|
||||||
|
|
||||||
static ObjectPayload getObjectPayload(long objectType,
|
|
||||||
long version,
|
|
||||||
long streamNumber,
|
|
||||||
InputStream stream,
|
|
||||||
int length) throws IOException {
|
|
||||||
ObjectType type = ObjectType.fromNumber(objectType);
|
|
||||||
if (type != null) {
|
|
||||||
switch (type) {
|
|
||||||
case GET_PUBKEY:
|
|
||||||
return parseGetPubkey(version, streamNumber, stream, length);
|
|
||||||
case PUBKEY:
|
|
||||||
return parsePubkey(version, streamNumber, stream, length);
|
|
||||||
case MSG:
|
|
||||||
return parseMsg(version, streamNumber, stream, length);
|
|
||||||
case BROADCAST:
|
|
||||||
return parseBroadcast(version, streamNumber, stream, length);
|
|
||||||
default:
|
|
||||||
LOG.error("This should not happen, someone broke something in the code!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// fallback: just store the message - we don't really care what it is
|
|
||||||
LOG.trace("Unexpected object type: " + objectType);
|
|
||||||
return GenericPayload.read(version, stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
return GetPubkey.read(stream, streamNumber, length, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException {
|
|
||||||
switch ((int) version) {
|
|
||||||
case 2:
|
|
||||||
return V2Pubkey.read(is, stream);
|
|
||||||
case 3:
|
|
||||||
return V3Pubkey.read(is, stream);
|
|
||||||
case 4:
|
|
||||||
return V4Pubkey.read(is, stream, length, encrypted);
|
|
||||||
}
|
|
||||||
LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true);
|
|
||||||
return pubkey != null ? pubkey : GenericPayload.read(version, stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
return Msg.read(stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException {
|
|
||||||
switch ((int) version) {
|
|
||||||
case 4:
|
|
||||||
return V4Broadcast.read(stream, streamNumber, length);
|
|
||||||
case 5:
|
|
||||||
return V5Broadcast.read(stream, streamNumber, length);
|
|
||||||
default:
|
|
||||||
LOG.debug("Encountered unknown broadcast version " + version);
|
|
||||||
return GenericPayload.read(version, stream, streamNumber, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectPayload getBroadcast(BitmessageAddress sendingAddress, Plaintext plaintext) {
|
|
||||||
if (sendingAddress.getVersion() < 4) {
|
|
||||||
return new V4Broadcast(sendingAddress, plaintext);
|
|
||||||
} else {
|
|
||||||
return new V5Broadcast(sendingAddress, plaintext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,226 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.factory;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.*;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.exception.NodeException;
|
|
||||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates protocol v3 network messages from {@link InputStream InputStreams}
|
|
||||||
*/
|
|
||||||
class V3MessageFactory {
|
|
||||||
private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class);
|
|
||||||
|
|
||||||
public static NetworkMessage read(InputStream in) throws IOException {
|
|
||||||
findMagic(in);
|
|
||||||
String command = getCommand(in);
|
|
||||||
int length = (int) Decode.uint32(in);
|
|
||||||
if (length > 1600003) {
|
|
||||||
throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
|
|
||||||
}
|
|
||||||
byte[] checksum = Decode.bytes(in, 4);
|
|
||||||
|
|
||||||
byte[] payloadBytes = Decode.bytes(in, length);
|
|
||||||
|
|
||||||
if (testChecksum(checksum, payloadBytes)) {
|
|
||||||
MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length);
|
|
||||||
if (payload != null)
|
|
||||||
return new NetworkMessage(payload);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
throw new IOException("Checksum failed for message '" + command + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
|
|
||||||
switch (command) {
|
|
||||||
case "version":
|
|
||||||
return parseVersion(stream);
|
|
||||||
case "verack":
|
|
||||||
return new VerAck();
|
|
||||||
case "addr":
|
|
||||||
return parseAddr(stream);
|
|
||||||
case "inv":
|
|
||||||
return parseInv(stream);
|
|
||||||
case "getdata":
|
|
||||||
return parseGetData(stream);
|
|
||||||
case "object":
|
|
||||||
return readObject(stream, length);
|
|
||||||
case "custom":
|
|
||||||
return readCustom(stream, length);
|
|
||||||
default:
|
|
||||||
LOG.debug("Unknown command: " + command);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MessagePayload readCustom(InputStream in, int length) throws IOException {
|
|
||||||
return CustomMessage.read(in, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
|
|
||||||
AccessCounter counter = new AccessCounter();
|
|
||||||
byte nonce[] = Decode.bytes(in, 8, counter);
|
|
||||||
long expiresTime = Decode.int64(in, counter);
|
|
||||||
long objectType = Decode.uint32(in, counter);
|
|
||||||
long version = Decode.varInt(in, counter);
|
|
||||||
long stream = Decode.varInt(in, counter);
|
|
||||||
|
|
||||||
byte[] data = Decode.bytes(in, length - counter.length());
|
|
||||||
ObjectPayload payload;
|
|
||||||
try {
|
|
||||||
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
|
|
||||||
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.trace("Could not parse object payload - using generic payload instead", e);
|
|
||||||
payload = new GenericPayload(version, stream, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ObjectMessage.Builder()
|
|
||||||
.nonce(nonce)
|
|
||||||
.expiresTime(expiresTime)
|
|
||||||
.objectType(objectType)
|
|
||||||
.stream(stream)
|
|
||||||
.payload(payload)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetData parseGetData(InputStream stream) throws IOException {
|
|
||||||
long count = Decode.varInt(stream);
|
|
||||||
GetData.Builder builder = new GetData.Builder();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
builder.addInventoryVector(parseInventoryVector(stream));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Inv parseInv(InputStream stream) throws IOException {
|
|
||||||
long count = Decode.varInt(stream);
|
|
||||||
Inv.Builder builder = new Inv.Builder();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
builder.addInventoryVector(parseInventoryVector(stream));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Addr parseAddr(InputStream stream) throws IOException {
|
|
||||||
long count = Decode.varInt(stream);
|
|
||||||
Addr.Builder builder = new Addr.Builder();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
builder.addAddress(parseAddress(stream, false));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Version parseVersion(InputStream stream) throws IOException {
|
|
||||||
int version = Decode.int32(stream);
|
|
||||||
long services = Decode.int64(stream);
|
|
||||||
long timestamp = Decode.int64(stream);
|
|
||||||
NetworkAddress addrRecv = parseAddress(stream, true);
|
|
||||||
NetworkAddress addrFrom = parseAddress(stream, true);
|
|
||||||
long nonce = Decode.int64(stream);
|
|
||||||
String userAgent = Decode.varString(stream);
|
|
||||||
long[] streamNumbers = Decode.varIntList(stream);
|
|
||||||
|
|
||||||
return new Version.Builder()
|
|
||||||
.version(version)
|
|
||||||
.services(services)
|
|
||||||
.timestamp(timestamp)
|
|
||||||
.addrRecv(addrRecv).addrFrom(addrFrom)
|
|
||||||
.nonce(nonce)
|
|
||||||
.userAgent(userAgent)
|
|
||||||
.streams(streamNumbers).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InventoryVector parseInventoryVector(InputStream stream) throws IOException {
|
|
||||||
return new InventoryVector(Decode.bytes(stream, 32));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException {
|
|
||||||
long time;
|
|
||||||
long streamNumber;
|
|
||||||
if (!light) {
|
|
||||||
time = Decode.int64(stream);
|
|
||||||
streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct
|
|
||||||
} else {
|
|
||||||
time = 0;
|
|
||||||
streamNumber = 0;
|
|
||||||
}
|
|
||||||
long services = Decode.int64(stream);
|
|
||||||
byte[] ipv6 = Decode.bytes(stream, 16);
|
|
||||||
int port = Decode.uint16(stream);
|
|
||||||
return new NetworkAddress.Builder().time(time).stream(streamNumber).services(services).ipv6(ipv6).port(port).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean testChecksum(byte[] checksum, byte[] payload) {
|
|
||||||
byte[] payloadChecksum = security().sha512(payload);
|
|
||||||
for (int i = 0; i < checksum.length; i++) {
|
|
||||||
if (checksum[i] != payloadChecksum[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getCommand(InputStream stream) throws IOException {
|
|
||||||
byte[] bytes = new byte[12];
|
|
||||||
int end = bytes.length;
|
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
|
||||||
bytes[i] = (byte) stream.read();
|
|
||||||
if (end == bytes.length) {
|
|
||||||
if (bytes[i] == 0) end = i;
|
|
||||||
} else {
|
|
||||||
if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new String(bytes, 0, end, "ASCII");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void findMagic(InputStream in) throws IOException {
|
|
||||||
int pos = 0;
|
|
||||||
for (int i = 0; i < 1620000; i++) {
|
|
||||||
byte b = (byte) in.read();
|
|
||||||
if (b == MAGIC_BYTES[pos]) {
|
|
||||||
if (pos + 1 == MAGIC_BYTES.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (pos > 0 && b == MAGIC_BYTES[0]) {
|
|
||||||
pos = 1;
|
|
||||||
} else {
|
|
||||||
pos = 0;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
throw new NodeException("Failed to find MAGIC bytes in stream");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,183 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.InternalContext;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Numbers.max;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder {
|
|
||||||
public static final Logger LOG = LoggerFactory.getLogger(Cryptography.class);
|
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
|
||||||
private static final BigInteger TWO = BigInteger.valueOf(2);
|
|
||||||
private static final BigInteger TWO_POW_64 = TWO.pow(64);
|
|
||||||
private static final BigInteger TWO_POW_16 = TWO.pow(16);
|
|
||||||
|
|
||||||
private final String provider;
|
|
||||||
private InternalContext context;
|
|
||||||
|
|
||||||
protected AbstractCryptography(String provider) {
|
|
||||||
this.provider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] sha512(byte[]... data) {
|
|
||||||
return hash("SHA-512", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] doubleSha512(byte[]... data) {
|
|
||||||
MessageDigest mda = md("SHA-512");
|
|
||||||
for (byte[] d : data) {
|
|
||||||
mda.update(d);
|
|
||||||
}
|
|
||||||
return mda.digest(mda.digest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] doubleSha512(byte[] data, int length) {
|
|
||||||
MessageDigest mda = md("SHA-512");
|
|
||||||
mda.update(data, 0, length);
|
|
||||||
return mda.digest(mda.digest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ripemd160(byte[]... data) {
|
|
||||||
return hash("RIPEMD160", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] doubleSha256(byte[] data, int length) {
|
|
||||||
MessageDigest mda = md("SHA-256");
|
|
||||||
mda.update(data, 0, length);
|
|
||||||
return mda.digest(mda.digest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] sha1(byte[]... data) {
|
|
||||||
return hash("SHA-1", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] randomBytes(int length) {
|
|
||||||
byte[] result = new byte[length];
|
|
||||||
RANDOM.nextBytes(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
|
||||||
long extraBytes, ProofOfWorkEngine.Callback callback) {
|
|
||||||
nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte());
|
|
||||||
extraBytes = max(extraBytes, context.getNetworkExtraBytes());
|
|
||||||
|
|
||||||
byte[] initialHash = getInitialHash(object);
|
|
||||||
|
|
||||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
|
||||||
|
|
||||||
context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
|
||||||
throws IOException {
|
|
||||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
|
||||||
byte[] value = doubleSha512(object.getNonce(), getInitialHash(object));
|
|
||||||
if (Bytes.lt(target, value, 8)) {
|
|
||||||
throw new InsufficientProofOfWorkException(target, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getInitialHash(ObjectMessage object) {
|
|
||||||
return sha512(object.getPayloadBytesWithoutNonce());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
|
||||||
if (nonceTrialsPerByte == 0) nonceTrialsPerByte = context.getNetworkNonceTrialsPerByte();
|
|
||||||
if (extraBytes == 0) extraBytes = context.getNetworkExtraBytes();
|
|
||||||
|
|
||||||
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
|
|
||||||
BigInteger numerator = TWO_POW_64;
|
|
||||||
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
|
|
||||||
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte)
|
|
||||||
.multiply(
|
|
||||||
powLength.add(
|
|
||||||
powLength.multiply(TTL).divide(TWO_POW_16)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] hash(String algorithm, byte[]... data) {
|
|
||||||
MessageDigest mda = md(algorithm);
|
|
||||||
for (byte[] d : data) {
|
|
||||||
mda.update(d);
|
|
||||||
}
|
|
||||||
return mda.digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageDigest md(String algorithm) {
|
|
||||||
try {
|
|
||||||
return MessageDigest.getInstance(algorithm, provider);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] mac(byte[] key_m, byte[] data) {
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance("HmacSHA256", provider);
|
|
||||||
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
|
|
||||||
return mac.doFinal(data);
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
|
||||||
return Factory.createPubkey(version, stream,
|
|
||||||
createPublicKey(privateSigningKey),
|
|
||||||
createPublicKey(privateEncryptionKey),
|
|
||||||
nonceTrialsPerByte, extraBytes, features);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigInteger keyToBigInt(byte[] privateKey) {
|
|
||||||
return new BigInteger(1, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long randomNonce() {
|
|
||||||
return RANDOM.nextLong();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface AddressRepository {
|
|
||||||
BitmessageAddress findContact(byte[] ripeOrTag);
|
|
||||||
|
|
||||||
BitmessageAddress findIdentity(byte[] ripeOrTag);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all Bitmessage addresses that belong to this user, i.e. have a private key.
|
|
||||||
*/
|
|
||||||
List<BitmessageAddress> getIdentities();
|
|
||||||
|
|
||||||
List<BitmessageAddress> getSubscriptions();
|
|
||||||
|
|
||||||
List<BitmessageAddress> getSubscriptions(long broadcastVersion);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all Bitmessage addresses that have no private key.
|
|
||||||
*/
|
|
||||||
List<BitmessageAddress> getContacts();
|
|
||||||
|
|
||||||
void save(BitmessageAddress address);
|
|
||||||
|
|
||||||
void remove(BitmessageAddress address);
|
|
||||||
|
|
||||||
BitmessageAddress getAddress(String address);
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Collections.selectRandom;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
|
|
||||||
import static java.util.Collections.newSetFromMap;
|
|
||||||
|
|
||||||
public class MemoryNodeRegistry implements NodeRegistry {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MemoryNodeRegistry.class);
|
|
||||||
|
|
||||||
private final Map<Long, Set<NetworkAddress>> stableNodes = new ConcurrentHashMap<>();
|
|
||||||
private final Map<Long, Set<NetworkAddress>> knownNodes = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private void loadStableNodes() {
|
|
||||||
try (InputStream in = getClass().getClassLoader().getResourceAsStream("nodes.txt")) {
|
|
||||||
Scanner scanner = new Scanner(in);
|
|
||||||
long stream = 0;
|
|
||||||
Set<NetworkAddress> streamSet = null;
|
|
||||||
while (scanner.hasNext()) {
|
|
||||||
try {
|
|
||||||
String line = scanner.nextLine().trim();
|
|
||||||
if (line.startsWith("#") || line.isEmpty()) {
|
|
||||||
// Ignore
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (line.startsWith("[stream")) {
|
|
||||||
stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
|
|
||||||
streamSet = new HashSet<>();
|
|
||||||
stableNodes.put(stream, streamSet);
|
|
||||||
} else if (streamSet != null) {
|
|
||||||
int portIndex = line.lastIndexOf(':');
|
|
||||||
InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex));
|
|
||||||
int port = Integer.valueOf(line.substring(portIndex + 1));
|
|
||||||
for (InetAddress inetAddress : inetAddresses) {
|
|
||||||
streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
for (Map.Entry<Long, Set<NetworkAddress>> e : stableNodes.entrySet()) {
|
|
||||||
LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
|
||||||
List<NetworkAddress> result = new LinkedList<>();
|
|
||||||
for (long stream : streams) {
|
|
||||||
Set<NetworkAddress> known = knownNodes.get(stream);
|
|
||||||
if (known != null && !known.isEmpty()) {
|
|
||||||
for (NetworkAddress node : known) {
|
|
||||||
if (node.getTime() > UnixTime.now(-3 * HOUR)) {
|
|
||||||
result.add(node);
|
|
||||||
} else {
|
|
||||||
known.remove(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Set<NetworkAddress> nodes = stableNodes.get(stream);
|
|
||||||
if (nodes == null || nodes.isEmpty()) {
|
|
||||||
loadStableNodes();
|
|
||||||
nodes = stableNodes.get(stream);
|
|
||||||
}
|
|
||||||
if (nodes != null && !nodes.isEmpty()) {
|
|
||||||
// To reduce load on stable nodes, only return one
|
|
||||||
result.add(selectRandom(nodes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return selectRandom(limit, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void offerAddresses(List<NetworkAddress> addresses) {
|
|
||||||
for (NetworkAddress node : addresses) {
|
|
||||||
if (node.getTime() <= UnixTime.now()) {
|
|
||||||
if (!knownNodes.containsKey(node.getStream())) {
|
|
||||||
synchronized (knownNodes) {
|
|
||||||
if (!knownNodes.containsKey(node.getStream())) {
|
|
||||||
knownNodes.put(
|
|
||||||
node.getStream(),
|
|
||||||
newSetFromMap(new ConcurrentHashMap<NetworkAddress, Boolean>())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.getTime() <= UnixTime.now()) {
|
|
||||||
// TODO: This isn't quite correct
|
|
||||||
// If the node is already known, the one with the more recent time should be used
|
|
||||||
knownNodes.get(node.getStream()).add(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext.Status;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface MessageRepository {
|
|
||||||
List<Label> getLabels();
|
|
||||||
|
|
||||||
List<Label> getLabels(Label.Type... types);
|
|
||||||
|
|
||||||
int countUnread(Label label);
|
|
||||||
|
|
||||||
Plaintext getMessage(byte[] initialHash);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Label label);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Status status);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Status status, BitmessageAddress recipient);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(BitmessageAddress sender);
|
|
||||||
|
|
||||||
void save(Plaintext message);
|
|
||||||
|
|
||||||
void remove(Plaintext message);
|
|
||||||
}
|
|
@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Semaphore;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A POW engine using all available CPU cores.
|
|
||||||
*/
|
|
||||||
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
|
|
||||||
private static final Semaphore semaphore = new Semaphore(1, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will block until all pending nonce calculations are done, but not wait for its own calculation
|
|
||||||
* to finish.
|
|
||||||
* (This implementation becomes very inefficient if multiple nonce are calculated at the same time.)
|
|
||||||
*
|
|
||||||
* @param initialHash the SHA-512 hash of the object to send, sans nonce
|
|
||||||
* @param target the target, representing an unsigned long
|
|
||||||
* @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
|
||||||
try {
|
|
||||||
semaphore.acquire();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
callback = new CallbackWrapper(callback);
|
|
||||||
int cores = Runtime.getRuntime().availableProcessors();
|
|
||||||
if (cores > 255) cores = 255;
|
|
||||||
LOG.info("Doing POW using " + cores + " cores");
|
|
||||||
List<Worker> workers = new ArrayList<>(cores);
|
|
||||||
for (int i = 0; i < cores; i++) {
|
|
||||||
Worker w = new Worker(workers, (byte) cores, i, initialHash, target, callback);
|
|
||||||
workers.add(w);
|
|
||||||
}
|
|
||||||
for (Worker w : workers) {
|
|
||||||
// Doing this in the previous loop might cause a ConcurrentModificationException in the worker
|
|
||||||
// if a worker finds a nonce while new ones are still being added.
|
|
||||||
w.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Worker extends Thread {
|
|
||||||
private final Callback callback;
|
|
||||||
private final byte numberOfCores;
|
|
||||||
private final List<Worker> workers;
|
|
||||||
private final byte[] initialHash;
|
|
||||||
private final byte[] target;
|
|
||||||
private final MessageDigest mda;
|
|
||||||
private final byte[] nonce = new byte[8];
|
|
||||||
|
|
||||||
public Worker(List<Worker> workers, byte numberOfCores, int core, byte[] initialHash, byte[] target,
|
|
||||||
Callback callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
this.numberOfCores = numberOfCores;
|
|
||||||
this.workers = workers;
|
|
||||||
this.initialHash = initialHash;
|
|
||||||
this.target = target;
|
|
||||||
this.nonce[7] = (byte) core;
|
|
||||||
try {
|
|
||||||
mda = MessageDigest.getInstance("SHA-512");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
do {
|
|
||||||
inc(nonce, numberOfCores);
|
|
||||||
mda.update(nonce);
|
|
||||||
mda.update(initialHash);
|
|
||||||
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
|
|
||||||
synchronized (callback) {
|
|
||||||
if (!Thread.interrupted()) {
|
|
||||||
for (Worker w : workers) {
|
|
||||||
w.interrupt();
|
|
||||||
}
|
|
||||||
// Clear interrupted flag for callback
|
|
||||||
Thread.interrupted();
|
|
||||||
callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} while (!Thread.interrupted());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CallbackWrapper implements Callback {
|
|
||||||
private final Callback callback;
|
|
||||||
private final long startTime;
|
|
||||||
private boolean waiting = true;
|
|
||||||
|
|
||||||
public CallbackWrapper(Callback callback) {
|
|
||||||
this.startTime = System.currentTimeMillis();
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
// Prevents the callback from being called twice if two nonces are found simultaneously
|
|
||||||
synchronized (this) {
|
|
||||||
if (waiting) {
|
|
||||||
semaphore.release();
|
|
||||||
LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds");
|
|
||||||
waiting = false;
|
|
||||||
callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Objects that proof of work is currently being done for.
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public interface ProofOfWorkRepository {
|
|
||||||
Item getItem(byte[] initialHash);
|
|
||||||
|
|
||||||
List<byte[]> getItems();
|
|
||||||
|
|
||||||
void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
|
|
||||||
|
|
||||||
void removeObject(byte[] initialHash);
|
|
||||||
|
|
||||||
class Item {
|
|
||||||
public final ObjectMessage object;
|
|
||||||
public final long nonceTrialsPerByte;
|
|
||||||
public final long extraBytes;
|
|
||||||
|
|
||||||
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
|
||||||
this.object = object;
|
|
||||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
|
||||||
this.extraBytes = extraBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
|
|
||||||
* <p>
|
|
||||||
* <strong>Warning:</strong> implementations probably depend on POW being asynchronous, that's
|
|
||||||
* another reason not to use this one.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class SimplePOWEngine implements ProofOfWorkEngine {
|
|
||||||
@Override
|
|
||||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
|
||||||
byte[] nonce = new byte[8];
|
|
||||||
MessageDigest mda;
|
|
||||||
try {
|
|
||||||
mda = MessageDigest.getInstance("SHA-512");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
inc(nonce);
|
|
||||||
mda.update(nonce);
|
|
||||||
mda.update(initialHash);
|
|
||||||
} while (Bytes.lt(target, mda.digest(mda.digest()), 8));
|
|
||||||
callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2011 Google Inc.
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.exception.AddressFormatException;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
import static java.util.Arrays.copyOfRange;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base58 encoder and decoder.
|
|
||||||
*
|
|
||||||
* @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily.
|
|
||||||
*/
|
|
||||||
public class Base58 {
|
|
||||||
private static final int[] INDEXES = new int[128];
|
|
||||||
private static char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (int i = 0; i < INDEXES.length; i++) {
|
|
||||||
INDEXES[i] = -1;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < ALPHABET.length; i++) {
|
|
||||||
INDEXES[ALPHABET[i]] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the given bytes in base58. No checksum is appended.
|
|
||||||
*
|
|
||||||
* @param input to encode
|
|
||||||
* @return base58 encoded input
|
|
||||||
*/
|
|
||||||
public static String encode(byte[] input) {
|
|
||||||
if (input.length == 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
input = copyOfRange(input, 0, input.length);
|
|
||||||
// Count leading zeroes.
|
|
||||||
int zeroCount = 0;
|
|
||||||
while (zeroCount < input.length && input[zeroCount] == 0) {
|
|
||||||
++zeroCount;
|
|
||||||
}
|
|
||||||
// The actual encoding.
|
|
||||||
byte[] temp = new byte[input.length * 2];
|
|
||||||
int j = temp.length;
|
|
||||||
|
|
||||||
int startAt = zeroCount;
|
|
||||||
while (startAt < input.length) {
|
|
||||||
byte mod = divmod58(input, startAt);
|
|
||||||
if (input[startAt] == 0) {
|
|
||||||
++startAt;
|
|
||||||
}
|
|
||||||
temp[--j] = (byte) ALPHABET[mod];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip extra '1' if there are some after decoding.
|
|
||||||
while (j < temp.length && temp[j] == ALPHABET[0]) {
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
// Add as many leading '1' as there were leading zeros.
|
|
||||||
while (--zeroCount >= 0) {
|
|
||||||
temp[--j] = (byte) ALPHABET[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] output = copyOfRange(temp, j, temp.length);
|
|
||||||
try {
|
|
||||||
return new String(output, "US-ASCII");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e); // Cannot happen.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] decode(String input) throws AddressFormatException {
|
|
||||||
if (input.length() == 0) {
|
|
||||||
return new byte[0];
|
|
||||||
}
|
|
||||||
byte[] input58 = new byte[input.length()];
|
|
||||||
// Transform the String to a base58 byte sequence
|
|
||||||
for (int i = 0; i < input.length(); ++i) {
|
|
||||||
char c = input.charAt(i);
|
|
||||||
|
|
||||||
int digit58 = -1;
|
|
||||||
if (c >= 0 && c < 128) {
|
|
||||||
digit58 = INDEXES[c];
|
|
||||||
}
|
|
||||||
if (digit58 < 0) {
|
|
||||||
throw new AddressFormatException("Illegal character " + c + " at " + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
input58[i] = (byte) digit58;
|
|
||||||
}
|
|
||||||
// Count leading zeroes
|
|
||||||
int zeroCount = 0;
|
|
||||||
while (zeroCount < input58.length && input58[zeroCount] == 0) {
|
|
||||||
++zeroCount;
|
|
||||||
}
|
|
||||||
// The encoding
|
|
||||||
byte[] temp = new byte[input.length()];
|
|
||||||
int j = temp.length;
|
|
||||||
|
|
||||||
int startAt = zeroCount;
|
|
||||||
while (startAt < input58.length) {
|
|
||||||
byte mod = divmod256(input58, startAt);
|
|
||||||
if (input58[startAt] == 0) {
|
|
||||||
++startAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
temp[--j] = mod;
|
|
||||||
}
|
|
||||||
// Do no add extra leading zeroes, move j to first non null byte.
|
|
||||||
while (j < temp.length && temp[j] == 0) {
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
return copyOfRange(temp, j - zeroCount, temp.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// number -> number / 58, returns number % 58
|
|
||||||
//
|
|
||||||
private static byte divmod58(byte[] number, int startAt) {
|
|
||||||
int remainder = 0;
|
|
||||||
for (int i = startAt; i < number.length; i++) {
|
|
||||||
int digit256 = (int) number[i] & 0xFF;
|
|
||||||
int temp = remainder * 256 + digit256;
|
|
||||||
|
|
||||||
number[i] = (byte) (temp / 58);
|
|
||||||
|
|
||||||
remainder = temp % 58;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (byte) remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// number -> number / 256, returns number % 256
|
|
||||||
//
|
|
||||||
private static byte divmod256(byte[] number58, int startAt) {
|
|
||||||
int remainder = 0;
|
|
||||||
for (int i = startAt; i < number58.length; i++) {
|
|
||||||
int digit58 = (int) number58[i] & 0xFF;
|
|
||||||
int temp = remainder * 58 + digit58;
|
|
||||||
|
|
||||||
number58[i] = (byte) (temp / 256);
|
|
||||||
|
|
||||||
remainder = temp % 256;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (byte) remainder;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,8 +21,14 @@ package ch.dissem.bitmessage.utils;
|
|||||||
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
|
* This is one part due to the fact that Java doesn't support unsigned numbers, and another
|
||||||
* part so we don't have to convert between byte arrays and numbers in time critical
|
* part so we don't have to convert between byte arrays and numbers in time critical
|
||||||
* situations.
|
* situations.
|
||||||
|
* <p>
|
||||||
|
* Note: This class can't yet be ported to Kotlin, as with Kotlin byte + byte = int, which
|
||||||
|
* would be rather inefficient in our case.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class Bytes {
|
public class Bytes {
|
||||||
|
public static final byte BYTE_0x80 = (byte) 0x80;
|
||||||
|
|
||||||
public static void inc(byte[] nonce) {
|
public static void inc(byte[] nonce) {
|
||||||
for (int i = nonce.length - 1; i >= 0; i--) {
|
for (int i = nonce.length - 1; i >= 0; i--) {
|
||||||
nonce[i]++;
|
nonce[i]++;
|
||||||
@ -82,11 +88,7 @@ public class Bytes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean lt(byte a, byte b) {
|
private static boolean lt(byte a, byte b) {
|
||||||
if (a < 0) return b < 0 && a < b;
|
return (a ^ BYTE_0x80) < (b ^ BYTE_0x80);
|
||||||
if (b < 0) return a >= 0 || a < b;
|
|
||||||
return a < b;
|
|
||||||
// This would be easier to understand, but is (slightly) slower:
|
|
||||||
// return (a & 0xff) < (b & 0xff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
public class Collections {
|
|
||||||
private final static Random RANDOM = new Random();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param count the number of elements to return (if possible)
|
|
||||||
* @param collection the collection to take samples from
|
|
||||||
* @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The
|
|
||||||
* result is by no means securely random, but should be random enough so not the same objects get selected over
|
|
||||||
* and over again.
|
|
||||||
*/
|
|
||||||
public static <T> List<T> selectRandom(int count, Collection<T> collection) {
|
|
||||||
ArrayList<T> result = new ArrayList<>(count);
|
|
||||||
if (collection.size() <= count) {
|
|
||||||
result.addAll(collection);
|
|
||||||
} else {
|
|
||||||
double collectionRest = collection.size();
|
|
||||||
double resultRest = count;
|
|
||||||
int skipMax = (int) Math.ceil(collectionRest / resultRest);
|
|
||||||
int skip = RANDOM.nextInt(skipMax);
|
|
||||||
for (T item : collection) {
|
|
||||||
collectionRest--;
|
|
||||||
if (skip > 0) {
|
|
||||||
skip--;
|
|
||||||
} else {
|
|
||||||
result.add(item);
|
|
||||||
resultRest--;
|
|
||||||
if (resultRest == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
skipMax = (int) Math.ceil(collectionRest / resultRest);
|
|
||||||
skip = RANDOM.nextInt(skipMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T selectRandom(Collection<T> collection) {
|
|
||||||
int index = RANDOM.nextInt(collection.size());
|
|
||||||
for (T item : collection) {
|
|
||||||
if (index == 0) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
index--;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Empty collection? Size: " + collection.size());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class DebugUtils {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class);
|
|
||||||
|
|
||||||
public static void saveToFile(ObjectMessage objectMessage) {
|
|
||||||
try {
|
|
||||||
File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv");
|
|
||||||
f.createNewFile();
|
|
||||||
objectMessage.write(new FileOutputStream(f));
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <K> void inc(Map<K, Integer> map, K key) {
|
|
||||||
if (map.containsKey(key)) {
|
|
||||||
map.put(key, map.get(key) + 1);
|
|
||||||
} else {
|
|
||||||
map.put(key, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles decoding simple types from byte stream, according to
|
|
||||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
|
||||||
*/
|
|
||||||
public class Decode {
|
|
||||||
public static byte[] shortVarBytes(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int length = uint16(stream, counter);
|
|
||||||
return bytes(stream, length, counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] varBytes(InputStream stream) throws IOException {
|
|
||||||
int length = (int) varInt(stream, null);
|
|
||||||
return bytes(stream, length, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] varBytes(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int length = (int) varInt(stream, counter);
|
|
||||||
return bytes(stream, length, counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] bytes(InputStream stream, int count) throws IOException {
|
|
||||||
return bytes(stream, count, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] bytes(InputStream stream, int count, AccessCounter counter) throws IOException {
|
|
||||||
byte[] result = new byte[count];
|
|
||||||
int off = 0;
|
|
||||||
while (off < count) {
|
|
||||||
int read = stream.read(result, off, count - off);
|
|
||||||
if (read < 0) {
|
|
||||||
throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off);
|
|
||||||
}
|
|
||||||
off += read;
|
|
||||||
}
|
|
||||||
inc(counter, count);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long[] varIntList(InputStream stream) throws IOException {
|
|
||||||
int length = (int) varInt(stream);
|
|
||||||
long[] result = new long[length];
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
result[i] = varInt(stream);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long varInt(InputStream stream) throws IOException {
|
|
||||||
return varInt(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long varInt(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int first = stream.read();
|
|
||||||
inc(counter);
|
|
||||||
switch (first) {
|
|
||||||
case 0xfd:
|
|
||||||
return uint16(stream, counter);
|
|
||||||
case 0xfe:
|
|
||||||
return uint32(stream, counter);
|
|
||||||
case 0xff:
|
|
||||||
return int64(stream, counter);
|
|
||||||
default:
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint8(InputStream stream) throws IOException {
|
|
||||||
return stream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint16(InputStream stream) throws IOException {
|
|
||||||
return uint16(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint16(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 2);
|
|
||||||
return stream.read() * 256 + stream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long uint32(InputStream stream) throws IOException {
|
|
||||||
return uint32(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long uint32(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 4);
|
|
||||||
return stream.read() * 16777216L + stream.read() * 65536L + stream.read() * 256L + stream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int int32(InputStream stream) throws IOException {
|
|
||||||
return int32(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int int32(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 4);
|
|
||||||
return ByteBuffer.wrap(bytes(stream, 4)).getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long int64(InputStream stream) throws IOException {
|
|
||||||
return int64(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long int64(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 8);
|
|
||||||
return ByteBuffer.wrap(bytes(stream, 8)).getLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String varString(InputStream stream) throws IOException {
|
|
||||||
return varString(stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String varString(InputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
int length = (int) varInt(stream, counter);
|
|
||||||
// FIXME: technically, it says the length in characters, but I think this one might be correct
|
|
||||||
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
|
|
||||||
return new String(bytes(stream, length, counter), "utf-8");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.Streamable;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.AccessCounter.inc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class handles encoding simple types from byte stream, according to
|
|
||||||
* https://bitmessage.org/wiki/Protocol_specification#Common_structures
|
|
||||||
*/
|
|
||||||
public class Encode {
|
|
||||||
public static void varIntList(long[] values, OutputStream stream) throws IOException {
|
|
||||||
varInt(values.length, stream);
|
|
||||||
for (long value : values) {
|
|
||||||
varInt(value, stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varInt(long value, OutputStream stream) throws IOException {
|
|
||||||
varInt(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
if (value < 0) {
|
|
||||||
// This is due to the fact that Java doesn't really support unsigned values.
|
|
||||||
// Please be aware that this might be an error due to a smaller negative value being cast to long.
|
|
||||||
// Normally, negative values shouldn't occur within the protocol, and I large enough longs
|
|
||||||
// to being recognized as negatives aren't realistic.
|
|
||||||
stream.write(0xff);
|
|
||||||
inc(counter);
|
|
||||||
int64(value, stream, counter);
|
|
||||||
} else if (value < 0xfd) {
|
|
||||||
int8(value, stream, counter);
|
|
||||||
} else if (value <= 0xffffL) {
|
|
||||||
stream.write(0xfd);
|
|
||||||
inc(counter);
|
|
||||||
int16(value, stream, counter);
|
|
||||||
} else if (value <= 0xffffffffL) {
|
|
||||||
stream.write(0xfe);
|
|
||||||
inc(counter);
|
|
||||||
int32(value, stream, counter);
|
|
||||||
} else {
|
|
||||||
stream.write(0xff);
|
|
||||||
inc(counter);
|
|
||||||
int64(value, stream, counter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int8(long value, OutputStream stream) throws IOException {
|
|
||||||
int8(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int8(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write((int) value);
|
|
||||||
inc(counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int16(long value, OutputStream stream) throws IOException {
|
|
||||||
int16(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int16(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write(ByteBuffer.allocate(4).putInt((int) value).array(), 2, 2);
|
|
||||||
inc(counter, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int32(long value, OutputStream stream) throws IOException {
|
|
||||||
int32(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int32(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write(ByteBuffer.allocate(4).putInt((int) value).array());
|
|
||||||
inc(counter, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int64(long value, OutputStream stream) throws IOException {
|
|
||||||
int64(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int64(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
stream.write(ByteBuffer.allocate(8).putLong(value).array());
|
|
||||||
inc(counter, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varString(String value, OutputStream out) throws IOException {
|
|
||||||
byte[] bytes = value.getBytes("utf-8");
|
|
||||||
// Technically, it says the length in characters, but I think this one might be correct.
|
|
||||||
// It doesn't really matter, as only ASCII characters are being used.
|
|
||||||
// see also Decode#varString()
|
|
||||||
varInt(bytes.length, out);
|
|
||||||
out.write(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varBytes(byte[] data, OutputStream out) throws IOException {
|
|
||||||
varInt(data.length, out);
|
|
||||||
out.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes a {@link Streamable} object and returns the byte array.
|
|
||||||
*
|
|
||||||
* @param streamable the object to be serialized
|
|
||||||
* @return an array of bytes representing the given streamable object.
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
*/
|
|
||||||
public static byte[] bytes(Streamable streamable) throws IOException {
|
|
||||||
if (streamable == null) return null;
|
|
||||||
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
streamable.write(stream);
|
|
||||||
return stream.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param streamable the object to be serialized
|
|
||||||
* @param padding the result will be padded such that its length is a multiple of <em>padding</em>
|
|
||||||
* @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding.
|
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
*/
|
|
||||||
public static byte[] bytes(Streamable streamable, int padding) throws IOException {
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
streamable.write(stream);
|
|
||||||
int offset = padding - stream.size() % padding;
|
|
||||||
int length = stream.size() + offset;
|
|
||||||
byte[] result = new byte[length];
|
|
||||||
stream.write(result, offset, stream.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class Numbers {
|
|
||||||
public static long max(long a, long b) {
|
|
||||||
return a > b ? a : b;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now
|
|
||||||
* used to contain different status information. It is by default displayed in some JSON inspired human readable
|
|
||||||
* notation, but you might only want to rely on the 'human readable' part.
|
|
||||||
* <p>
|
|
||||||
* If you need a real JSON representation, please add a method <code>toJson()</code>.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class Property {
|
|
||||||
private String name;
|
|
||||||
private Object value;
|
|
||||||
private Property[] properties;
|
|
||||||
|
|
||||||
public Property(String name, Object value, Property... properties) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the property if available or <code>null</code> otherwise.
|
|
||||||
* Subproperties can be requested by submitting the sequence of properties.
|
|
||||||
*/
|
|
||||||
public Property getProperty(String... name) {
|
|
||||||
if (name == null || name.length == 0) return null;
|
|
||||||
|
|
||||||
for (Property p : properties) {
|
|
||||||
if (Objects.equals(name[0], p.name)) {
|
|
||||||
if (name.length == 1)
|
|
||||||
return p;
|
|
||||||
else
|
|
||||||
return p.getProperty(Arrays.copyOfRange(name, 1, name.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property[] getProperties() {
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toString("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toString(String indentation) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
result.append(indentation).append(name).append(": ");
|
|
||||||
if (value != null || properties.length == 0) {
|
|
||||||
result.append(value);
|
|
||||||
}
|
|
||||||
if (properties.length > 0) {
|
|
||||||
result.append("{\n");
|
|
||||||
for (Property property : properties) {
|
|
||||||
result.append(property.toString(indentation + " ")).append('\n');
|
|
||||||
}
|
|
||||||
result.append(indentation).append("}");
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores times to live for different object types. Usually this shouldn't be messed with,
|
|
||||||
* but for tests it might be a good idea to reduce it to a minimum, and on mobile clients
|
|
||||||
* you might want to optimize it as well.
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class TTL {
|
|
||||||
private static long msg = 2 * DAY;
|
|
||||||
private static long getpubkey = 2 * DAY;
|
|
||||||
private static long pubkey = 28 * DAY;
|
|
||||||
|
|
||||||
public static long msg() {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void msg(long msg) {
|
|
||||||
TTL.msg = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getpubkey() {
|
|
||||||
return getpubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void getpubkey(long getpubkey) {
|
|
||||||
TTL.getpubkey = getpubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long pubkey() {
|
|
||||||
return pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void pubkey(long pubkey) {
|
|
||||||
TTL.pubkey = pubkey;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Christian Basler
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package ch.dissem.bitmessage.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple utility class that simplifies using the second based time used in Bitmessage.
|
|
||||||
*/
|
|
||||||
public class UnixTime {
|
|
||||||
/**
|
|
||||||
* Length of a minute in seconds, intended for use with {@link #now(long)}.
|
|
||||||
*/
|
|
||||||
public static final int MINUTE = 60;
|
|
||||||
/**
|
|
||||||
* Length of an hour in seconds, intended for use with {@link #now(long)}.
|
|
||||||
*/
|
|
||||||
public static final long HOUR = 60 * MINUTE;
|
|
||||||
/**
|
|
||||||
* Length of a day in seconds, intended for use with {@link #now(long)}.
|
|
||||||
*/
|
|
||||||
public static final long DAY = 24 * HOUR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the time in second based Unix time ({@link System#currentTimeMillis()}/1000)
|
|
||||||
*/
|
|
||||||
public static long now() {
|
|
||||||
return System.currentTimeMillis() / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as {@link #now()} + shiftSeconds, but might be more readable.
|
|
||||||
*
|
|
||||||
* @param shiftSeconds number of seconds from now we're interested in
|
|
||||||
* @return the Unix time in shiftSeconds seconds / shiftSeconds seconds ago
|
|
||||||
*/
|
|
||||||
public static long now(long shiftSeconds) {
|
|
||||||
return (System.currentTimeMillis() / 1000) + shiftSeconds;
|
|
||||||
}
|
|
||||||
}
|
|
500
core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt
Normal file
500
core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt
Normal file
@ -0,0 +1,500 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.CustomMessage
|
||||||
|
import ch.dissem.bitmessage.entity.MessagePayload
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Status.DRAFT
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Broadcast
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.ports.*
|
||||||
|
import ch.dissem.bitmessage.utils.Property
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.HOUR
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Use this class if you want to create a Bitmessage client.
|
||||||
|
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
||||||
|
*
|
||||||
|
* * addressRepo
|
||||||
|
* * inventory
|
||||||
|
* * nodeRegistry
|
||||||
|
* * networkHandler
|
||||||
|
* * messageRepo
|
||||||
|
* * streams
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The default implementations in the different module builds can be used.
|
||||||
|
*
|
||||||
|
* The port defaults to 8444 (the default Bitmessage port)
|
||||||
|
*/
|
||||||
|
class BitmessageContext(
|
||||||
|
cryptography: Cryptography,
|
||||||
|
inventory: Inventory,
|
||||||
|
nodeRegistry: NodeRegistry,
|
||||||
|
networkHandler: NetworkHandler,
|
||||||
|
addressRepository: AddressRepository,
|
||||||
|
messageRepository: MessageRepository,
|
||||||
|
proofOfWorkRepository: ProofOfWorkRepository,
|
||||||
|
proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(),
|
||||||
|
customCommandHandler: CustomCommandHandler = object : CustomCommandHandler {
|
||||||
|
override fun handle(request: CustomMessage): MessagePayload? {
|
||||||
|
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
listener: Listener,
|
||||||
|
labeler: Labeler = DefaultLabeler(),
|
||||||
|
userAgent: String? = null,
|
||||||
|
port: Int = 8444,
|
||||||
|
connectionTTL: Long = 30 * MINUTE,
|
||||||
|
connectionLimit: Int = 150,
|
||||||
|
sendPubkeyOnIdentityCreation: Boolean = true,
|
||||||
|
doMissingProofOfWorkDelayInSeconds: Int = 30
|
||||||
|
) {
|
||||||
|
|
||||||
|
private constructor(builder: BitmessageContext.Builder) : this(
|
||||||
|
builder.cryptography,
|
||||||
|
builder.inventory,
|
||||||
|
builder.nodeRegistry,
|
||||||
|
builder.networkHandler,
|
||||||
|
builder.addressRepo,
|
||||||
|
builder.messageRepo,
|
||||||
|
builder.proofOfWorkRepository,
|
||||||
|
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
||||||
|
builder.customCommandHandler ?: object : CustomCommandHandler {
|
||||||
|
override fun handle(request: CustomMessage): MessagePayload? {
|
||||||
|
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder.listener,
|
||||||
|
builder.labeler ?: DefaultLabeler(),
|
||||||
|
builder.userAgent,
|
||||||
|
builder.port,
|
||||||
|
builder.connectionTTL,
|
||||||
|
builder.connectionLimit,
|
||||||
|
builder.sendPubkeyOnIdentityCreation,
|
||||||
|
builder.doMissingProofOfWorkDelay
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sendPubkeyOnIdentityCreation: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [InternalContext] - normally you wouldn't need it,
|
||||||
|
* unless you are doing something crazy with the protocol.
|
||||||
|
*/
|
||||||
|
val internals: InternalContext
|
||||||
|
@JvmName("internals") get
|
||||||
|
|
||||||
|
val labeler: Labeler
|
||||||
|
@JvmName("labeler") get
|
||||||
|
|
||||||
|
val addresses: AddressRepository
|
||||||
|
@JvmName("addresses") get
|
||||||
|
|
||||||
|
val messages: MessageRepository
|
||||||
|
@JvmName("messages") get
|
||||||
|
|
||||||
|
fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress {
|
||||||
|
val identity = BitmessageAddress(PrivateKey(
|
||||||
|
shorter,
|
||||||
|
internals.streams[0],
|
||||||
|
NETWORK_NONCE_TRIALS_PER_BYTE,
|
||||||
|
NETWORK_EXTRA_BYTES,
|
||||||
|
*features
|
||||||
|
))
|
||||||
|
internals.addressRepository.save(identity)
|
||||||
|
if (sendPubkeyOnIdentityCreation) {
|
||||||
|
internals.sendPubkey(identity, identity.stream)
|
||||||
|
}
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
|
||||||
|
fun joinChan(passphrase: String, address: String): BitmessageAddress {
|
||||||
|
val chan = BitmessageAddress.chan(address, passphrase)
|
||||||
|
chan.alias = passphrase
|
||||||
|
internals.addressRepository.save(chan)
|
||||||
|
return chan
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createChan(passphrase: String): BitmessageAddress {
|
||||||
|
// FIXME: hardcoded stream number
|
||||||
|
val chan = BitmessageAddress.chan(1, passphrase)
|
||||||
|
internals.addressRepository.save(chan)
|
||||||
|
return chan
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createDeterministicAddresses(
|
||||||
|
passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
|
||||||
|
val result = BitmessageAddress.deterministic(
|
||||||
|
passphrase, numberOfAddresses, version, stream, shorter)
|
||||||
|
for (i in result.indices) {
|
||||||
|
val address = result[i]
|
||||||
|
address.alias = "deterministic (" + (i + 1) + ")"
|
||||||
|
internals.addressRepository.save(address)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun broadcast(from: BitmessageAddress, subject: String, message: String) {
|
||||||
|
send(Plaintext(
|
||||||
|
type = BROADCAST,
|
||||||
|
from = from,
|
||||||
|
subject = subject,
|
||||||
|
body = message,
|
||||||
|
status = DRAFT
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun send(from: BitmessageAddress, to: BitmessageAddress, subject: String, message: String) {
|
||||||
|
if (from.privateKey == null) {
|
||||||
|
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||||
|
}
|
||||||
|
send(Plaintext(
|
||||||
|
type = MSG,
|
||||||
|
from = from,
|
||||||
|
to = to,
|
||||||
|
subject = subject,
|
||||||
|
body = message
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun send(msg: Plaintext) {
|
||||||
|
if (msg.from.privateKey == null) {
|
||||||
|
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||||
|
}
|
||||||
|
labeler.markAsSending(msg)
|
||||||
|
val to = msg.to
|
||||||
|
if (to != null) {
|
||||||
|
if (to.pubkey == null) {
|
||||||
|
LOG.info("Public key is missing from recipient. Requesting.")
|
||||||
|
internals.requestPubkey(to)
|
||||||
|
}
|
||||||
|
if (to.pubkey == null) {
|
||||||
|
internals.messageRepository.save(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (to == null || to.pubkey != null) {
|
||||||
|
LOG.info("Sending message.")
|
||||||
|
internals.messageRepository.save(msg)
|
||||||
|
if (msg.type == MSG) {
|
||||||
|
internals.send(msg)
|
||||||
|
} else {
|
||||||
|
internals.send(
|
||||||
|
msg.from,
|
||||||
|
to,
|
||||||
|
Factory.getBroadcast(msg),
|
||||||
|
msg.ttl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startup() {
|
||||||
|
internals.networkHandler.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shutdown() {
|
||||||
|
internals.networkHandler.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
||||||
|
* @param port of the trusted host, default is 8444
|
||||||
|
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed,
|
||||||
|
* even if not all objects were fetched
|
||||||
|
* @param wait waits for the synchronization thread to finish
|
||||||
|
*/
|
||||||
|
fun synchronize(host: InetAddress, port: Int, timeoutInSeconds: Long, wait: Boolean) {
|
||||||
|
val future = internals.networkHandler.synchronize(host, port, timeoutInSeconds)
|
||||||
|
if (wait) {
|
||||||
|
try {
|
||||||
|
future.get()
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.")
|
||||||
|
future.cancel(true)
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
||||||
|
* the response, which in turn is expected to be a [CustomMessage].
|
||||||
|
*
|
||||||
|
* @param server the node's address
|
||||||
|
* @param port the node's port
|
||||||
|
* @param request the request
|
||||||
|
* @return the response
|
||||||
|
*/
|
||||||
|
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
|
||||||
|
return internals.networkHandler.send(server, port, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes expired objects from the inventory. You should call this method regularly,
|
||||||
|
* e.g. daily and on each shutdown.
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
internals.inventory.cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends messages again whose time to live expired without being acknowledged. (And whose
|
||||||
|
* recipient is expected to send acknowledgements.
|
||||||
|
*
|
||||||
|
* You should call this method regularly, but be aware of the following:
|
||||||
|
*
|
||||||
|
* * As messages might be sent, POW will be done. It is therefore not advised to
|
||||||
|
* call it on shutdown.
|
||||||
|
* * It shouldn't be called right after startup, as it's possible the missing
|
||||||
|
* acknowledgement was sent while the client was offline.
|
||||||
|
* * Other than that, the call isn't expensive as long as there is no message
|
||||||
|
* to send, so it might be a good idea to just call it every few minutes.
|
||||||
|
*/
|
||||||
|
fun resendUnacknowledgedMessages() {
|
||||||
|
internals.resendUnacknowledged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isRunning() = internals.networkHandler.isRunning
|
||||||
|
|
||||||
|
fun addContact(contact: BitmessageAddress) {
|
||||||
|
internals.addressRepository.save(contact)
|
||||||
|
if (contact.pubkey == null) {
|
||||||
|
// If it already existed, the saved contact might have the public key
|
||||||
|
if (internals.addressRepository.getAddress(contact.address)!!.pubkey == null) {
|
||||||
|
internals.requestPubkey(contact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addSubscribtion(address: BitmessageAddress) {
|
||||||
|
address.isSubscribed = true
|
||||||
|
internals.addressRepository.save(address)
|
||||||
|
tryToFindBroadcastsForAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) {
|
||||||
|
for (objectMessage in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) {
|
||||||
|
try {
|
||||||
|
val broadcast = objectMessage.payload as Broadcast
|
||||||
|
broadcast.decrypt(address)
|
||||||
|
// This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with
|
||||||
|
// other subscriptions and the interface stays as simple as possible.
|
||||||
|
internals.networkListener.receive(objectMessage)
|
||||||
|
} catch (ignore: DecryptionFailedException) {
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun status(): Property {
|
||||||
|
return Property("status",
|
||||||
|
Property("user agent", internals.userAgent),
|
||||||
|
internals.networkHandler.getNetworkStatus(),
|
||||||
|
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun receive(plaintext: Plaintext)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message listener that needs a [BitmessageContext], i.e. for implementing some sort of chat bot.
|
||||||
|
*/
|
||||||
|
interface WithContext : Listener {
|
||||||
|
fun setContext(ctx: BitmessageContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
internal var port = 8444
|
||||||
|
internal var inventory by Delegates.notNull<Inventory>()
|
||||||
|
internal var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
||||||
|
internal var networkHandler by Delegates.notNull<NetworkHandler>()
|
||||||
|
internal var addressRepo by Delegates.notNull<AddressRepository>()
|
||||||
|
internal var messageRepo by Delegates.notNull<MessageRepository>()
|
||||||
|
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>()
|
||||||
|
internal var proofOfWorkEngine: ProofOfWorkEngine? = null
|
||||||
|
internal var cryptography by Delegates.notNull<Cryptography>()
|
||||||
|
internal var customCommandHandler: CustomCommandHandler? = null
|
||||||
|
internal var labeler: Labeler? = null
|
||||||
|
internal var userAgent: String? = null
|
||||||
|
internal var listener by Delegates.notNull<Listener>()
|
||||||
|
internal var connectionLimit = 150
|
||||||
|
internal var connectionTTL = 30 * MINUTE
|
||||||
|
internal var sendPubkeyOnIdentityCreation = true
|
||||||
|
internal var doMissingProofOfWorkDelay = 30
|
||||||
|
|
||||||
|
fun port(port: Int): Builder {
|
||||||
|
this.port = port
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun inventory(inventory: Inventory): Builder {
|
||||||
|
this.inventory = inventory
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nodeRegistry(nodeRegistry: NodeRegistry): Builder {
|
||||||
|
this.nodeRegistry = nodeRegistry
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun networkHandler(networkHandler: NetworkHandler): Builder {
|
||||||
|
this.networkHandler = networkHandler
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addressRepo(addressRepo: AddressRepository): Builder {
|
||||||
|
this.addressRepo = addressRepo
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun messageRepo(messageRepo: MessageRepository): Builder {
|
||||||
|
this.messageRepo = messageRepo
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
|
||||||
|
this.proofOfWorkRepository = proofOfWorkRepository
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cryptography(cryptography: Cryptography): Builder {
|
||||||
|
this.cryptography = cryptography
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun customCommandHandler(handler: CustomCommandHandler): Builder {
|
||||||
|
this.customCommandHandler = handler
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun proofOfWorkEngine(proofOfWorkEngine: ProofOfWorkEngine): Builder {
|
||||||
|
this.proofOfWorkEngine = proofOfWorkEngine
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun labeler(labeler: Labeler): Builder {
|
||||||
|
this.labeler = labeler
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listener(listener: Listener): Builder {
|
||||||
|
this.listener = listener
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("kotlinListener")
|
||||||
|
fun listener(listener: (Plaintext) -> Unit): Builder {
|
||||||
|
this.listener = object : Listener {
|
||||||
|
override fun receive(plaintext: Plaintext) {
|
||||||
|
listener.invoke(plaintext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connectionLimit(connectionLimit: Int): Builder {
|
||||||
|
this.connectionLimit = connectionLimit
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connectionTTL(hours: Int): Builder {
|
||||||
|
this.connectionTTL = hours * HOUR
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doMissingProofOfWorkDelay(seconds: Int) {
|
||||||
|
this.doMissingProofOfWorkDelay = seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||||
|
* this behaviour might not be desirable.
|
||||||
|
*/
|
||||||
|
fun doNotSendPubkeyOnIdentityCreation(): Builder {
|
||||||
|
this.sendPubkeyOnIdentityCreation = false
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): BitmessageContext {
|
||||||
|
return BitmessageContext(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.labeler = labeler
|
||||||
|
this.internals = InternalContext(
|
||||||
|
cryptography,
|
||||||
|
inventory,
|
||||||
|
nodeRegistry,
|
||||||
|
networkHandler,
|
||||||
|
addressRepository,
|
||||||
|
messageRepository,
|
||||||
|
proofOfWorkRepository,
|
||||||
|
proofOfWorkEngine,
|
||||||
|
customCommandHandler,
|
||||||
|
listener,
|
||||||
|
labeler,
|
||||||
|
userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/",
|
||||||
|
port,
|
||||||
|
connectionTTL,
|
||||||
|
connectionLimit
|
||||||
|
)
|
||||||
|
this.addresses = addressRepository
|
||||||
|
this.messages = messageRepository
|
||||||
|
this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation
|
||||||
|
(listener as? Listener.WithContext)?.setContext(this)
|
||||||
|
internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val CURRENT_VERSION = 3
|
||||||
|
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
|
||||||
|
|
||||||
|
val version: String by lazy {
|
||||||
|
BitmessageContext::class.java.getResource("/version")?.readText() ?: "local build"
|
||||||
|
}
|
||||||
|
@JvmStatic get
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED
|
||||||
|
import ch.dissem.bitmessage.entity.payload.*
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.ports.AlreadyStoredException
|
||||||
|
import ch.dissem.bitmessage.ports.Labeler
|
||||||
|
import ch.dissem.bitmessage.ports.NetworkHandler
|
||||||
|
import ch.dissem.bitmessage.utils.Strings.hex
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
open class DefaultMessageListener(
|
||||||
|
private val labeler: Labeler,
|
||||||
|
private val listener: BitmessageContext.Listener
|
||||||
|
) : NetworkHandler.MessageListener, InternalContext.ContextHolder {
|
||||||
|
private lateinit var ctx: InternalContext
|
||||||
|
|
||||||
|
override fun setContext(context: InternalContext) {
|
||||||
|
ctx = context
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun receive(objectMessage: ObjectMessage) {
|
||||||
|
val payload = objectMessage.payload
|
||||||
|
|
||||||
|
when (payload.type) {
|
||||||
|
ObjectType.GET_PUBKEY -> {
|
||||||
|
receive(objectMessage, payload as GetPubkey)
|
||||||
|
}
|
||||||
|
ObjectType.PUBKEY -> {
|
||||||
|
receive(payload as Pubkey)
|
||||||
|
}
|
||||||
|
ObjectType.MSG -> {
|
||||||
|
receive(objectMessage, payload as Msg)
|
||||||
|
}
|
||||||
|
ObjectType.BROADCAST -> {
|
||||||
|
receive(objectMessage, payload as Broadcast)
|
||||||
|
}
|
||||||
|
null -> {
|
||||||
|
if (payload is GenericPayload) {
|
||||||
|
receive(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun receive(objectMessage: ObjectMessage, getPubkey: GetPubkey) {
|
||||||
|
val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag)
|
||||||
|
if (identity?.privateKey != null && !identity.isChan) {
|
||||||
|
LOG.info("Got pubkey request for identity " + identity)
|
||||||
|
// FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days
|
||||||
|
ctx.sendPubkey(identity, objectMessage.stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun receive(pubkey: Pubkey) {
|
||||||
|
try {
|
||||||
|
if (pubkey is V4Pubkey) {
|
||||||
|
ctx.addressRepository.findContact(pubkey.tag)?.let {
|
||||||
|
if (it.pubkey == null) {
|
||||||
|
pubkey.decrypt(it.publicDecryptionKey)
|
||||||
|
updatePubkey(it, pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.addressRepository.findContact(pubkey.ripe)?.let {
|
||||||
|
if (it.pubkey == null) {
|
||||||
|
updatePubkey(it, pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: DecryptionFailedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePubkey(address: BitmessageAddress, pubkey: Pubkey) {
|
||||||
|
address.pubkey = pubkey
|
||||||
|
LOG.info("Got pubkey for contact " + address)
|
||||||
|
ctx.addressRepository.save(address)
|
||||||
|
val messages = ctx.messageRepository.findMessages(PUBKEY_REQUESTED, address)
|
||||||
|
LOG.info("Sending " + messages.size + " messages for contact " + address)
|
||||||
|
for (msg in messages) {
|
||||||
|
ctx.labeler.markAsSending(msg)
|
||||||
|
ctx.messageRepository.save(msg)
|
||||||
|
ctx.send(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun receive(objectMessage: ObjectMessage, msg: Msg) {
|
||||||
|
for (identity in ctx.addressRepository.getIdentities()) {
|
||||||
|
try {
|
||||||
|
msg.decrypt(identity.privateKey!!.privateEncryptionKey)
|
||||||
|
val plaintext = msg.plaintext!!
|
||||||
|
plaintext.to = identity
|
||||||
|
if (!objectMessage.isSignatureValid(plaintext.from.pubkey!!)) {
|
||||||
|
LOG.warn("Msg with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
|
||||||
|
} else {
|
||||||
|
receive(objectMessage.inventoryVector, plaintext)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} catch (_: DecryptionFailedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun receive(ack: GenericPayload) {
|
||||||
|
if (ack.data.size == Msg.ACK_LENGTH) {
|
||||||
|
ctx.messageRepository.getMessageForAck(ack.data)?.let {
|
||||||
|
ctx.labeler.markAsAcknowledged(it)
|
||||||
|
ctx.messageRepository.save(it)
|
||||||
|
} ?: LOG.debug("Message not found for ack ${hex(ack.data)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun receive(objectMessage: ObjectMessage, broadcast: Broadcast) {
|
||||||
|
val tag = (broadcast as? V5Broadcast)?.tag
|
||||||
|
ctx.addressRepository.getSubscriptions(broadcast.version)
|
||||||
|
.filter { tag == null || Arrays.equals(tag, it.tag) }
|
||||||
|
.forEach {
|
||||||
|
try {
|
||||||
|
broadcast.decrypt(it.publicDecryptionKey)
|
||||||
|
if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) {
|
||||||
|
LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
|
||||||
|
} else {
|
||||||
|
receive(objectMessage.inventoryVector, broadcast.plaintext!!)
|
||||||
|
}
|
||||||
|
} catch (_: DecryptionFailedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun receive(iv: InventoryVector, msg: Plaintext) {
|
||||||
|
val contact = ctx.addressRepository.getAddress(msg.from.address)
|
||||||
|
if (contact != null && contact.pubkey == null) {
|
||||||
|
updatePubkey(contact, msg.from.pubkey!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.inventoryVector = iv
|
||||||
|
try {
|
||||||
|
ctx.messageRepository.save(msg)
|
||||||
|
// We might need the ID here, so we need to add the labels and save it again
|
||||||
|
labeler.setLabels(msg)
|
||||||
|
ctx.messageRepository.save(msg)
|
||||||
|
listener.receive(msg)
|
||||||
|
|
||||||
|
if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) {
|
||||||
|
msg.ackMessage?.let {
|
||||||
|
ctx.inventory.storeObject(it)
|
||||||
|
ctx.networkHandler.offer(it.inventoryVector)
|
||||||
|
} ?: LOG.debug("ack message expected")
|
||||||
|
}
|
||||||
|
} catch (e: AlreadyStoredException) {
|
||||||
|
LOG.trace("Message was already received before.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(DefaultMessageListener::class.java)
|
||||||
|
}
|
||||||
|
}
|
226
core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt
Normal file
226
core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Encrypted
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.payload.*
|
||||||
|
import ch.dissem.bitmessage.ports.*
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton
|
||||||
|
import ch.dissem.bitmessage.utils.TTL
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal context should normally only be used for port implementations. If you need it in your client
|
||||||
|
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
|
||||||
|
* get extended.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class InternalContext(
|
||||||
|
val cryptography: Cryptography,
|
||||||
|
val inventory: ch.dissem.bitmessage.ports.Inventory,
|
||||||
|
val nodeRegistry: NodeRegistry,
|
||||||
|
val networkHandler: NetworkHandler,
|
||||||
|
val addressRepository: AddressRepository,
|
||||||
|
val messageRepository: ch.dissem.bitmessage.ports.MessageRepository,
|
||||||
|
val proofOfWorkRepository: ProofOfWorkRepository,
|
||||||
|
val proofOfWorkEngine: ProofOfWorkEngine,
|
||||||
|
val customCommandHandler: CustomCommandHandler,
|
||||||
|
listener: BitmessageContext.Listener,
|
||||||
|
val labeler: Labeler,
|
||||||
|
|
||||||
|
val userAgent: String,
|
||||||
|
|
||||||
|
val port: Int,
|
||||||
|
val connectionTTL: Long,
|
||||||
|
val connectionLimit: Int
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val threadPool = Executors.newCachedThreadPool()
|
||||||
|
|
||||||
|
val proofOfWorkService: ProofOfWorkService = ProofOfWorkService()
|
||||||
|
val networkListener: NetworkHandler.MessageListener = DefaultMessageListener(labeler, listener)
|
||||||
|
val clientNonce: Long = cryptography.randomNonce()
|
||||||
|
private val _streams = TreeSet<Long>()
|
||||||
|
val streams: LongArray
|
||||||
|
get() = _streams.toLongArray()
|
||||||
|
|
||||||
|
init {
|
||||||
|
Singleton.initialize(cryptography)
|
||||||
|
|
||||||
|
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
|
||||||
|
addressRepository.getIdentities().mapTo(_streams) { it.stream }
|
||||||
|
addressRepository.getSubscriptions().mapTo(_streams) { it.stream }
|
||||||
|
if (_streams.isEmpty()) {
|
||||||
|
_streams.add(1L)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
|
||||||
|
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, labeler,
|
||||||
|
networkListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init(vararg objects: Any) {
|
||||||
|
objects.filter { it is ContextHolder }.forEach { (it as ContextHolder).setContext(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun send(plaintext: Plaintext) {
|
||||||
|
if (plaintext.ackMessage != null) {
|
||||||
|
val expires = UnixTime.now + plaintext.ttl
|
||||||
|
LOG.info("Expires at " + expires)
|
||||||
|
proofOfWorkService.doProofOfWorkWithAck(plaintext, expires)
|
||||||
|
} else {
|
||||||
|
send(plaintext.from, plaintext.to, Msg(plaintext), plaintext.ttl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun send(from: BitmessageAddress, to: BitmessageAddress?, payload: ObjectPayload,
|
||||||
|
timeToLive: Long) {
|
||||||
|
val recipient = to ?: from
|
||||||
|
val expires = UnixTime.now + timeToLive
|
||||||
|
LOG.info("Expires at " + expires)
|
||||||
|
val objectMessage = ObjectMessage(
|
||||||
|
stream = recipient.stream,
|
||||||
|
expiresTime = expires,
|
||||||
|
payload = payload
|
||||||
|
)
|
||||||
|
if (objectMessage.isSigned) {
|
||||||
|
objectMessage.sign(
|
||||||
|
from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (payload is Broadcast) {
|
||||||
|
payload.encrypt()
|
||||||
|
} else if (payload is Encrypted) {
|
||||||
|
objectMessage.encrypt(
|
||||||
|
recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
proofOfWorkService.doProofOfWork(to, objectMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendPubkey(identity: BitmessageAddress, targetStream: Long) {
|
||||||
|
val expires = UnixTime.now + TTL.pubkey
|
||||||
|
LOG.info("Expires at " + expires)
|
||||||
|
val payload = identity.pubkey ?: throw IllegalArgumentException("The given address is no identity")
|
||||||
|
val response = ObjectMessage(
|
||||||
|
expiresTime = expires,
|
||||||
|
stream = targetStream,
|
||||||
|
payload = payload
|
||||||
|
)
|
||||||
|
response.sign(
|
||||||
|
identity.privateKey ?: throw IllegalArgumentException("The given address is no identity")
|
||||||
|
)
|
||||||
|
response.encrypt(cryptography.createPublicKey(identity.publicDecryptionKey))
|
||||||
|
// TODO: remember that the pubkey is just about to be sent, and on which stream!
|
||||||
|
proofOfWorkService.doProofOfWork(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback
|
||||||
|
* for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB.
|
||||||
|
*/
|
||||||
|
fun requestPubkey(contact: BitmessageAddress) {
|
||||||
|
threadPool.execute {
|
||||||
|
val stored = addressRepository.getAddress(contact.address)
|
||||||
|
|
||||||
|
tryToFindMatchingPubkey(contact)
|
||||||
|
if (contact.pubkey != null) {
|
||||||
|
if (stored != null) {
|
||||||
|
stored.pubkey = contact.pubkey
|
||||||
|
addressRepository.save(stored)
|
||||||
|
} else {
|
||||||
|
addressRepository.save(contact)
|
||||||
|
}
|
||||||
|
return@execute
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stored == null) {
|
||||||
|
addressRepository.save(contact)
|
||||||
|
}
|
||||||
|
|
||||||
|
val expires = UnixTime.now + TTL.getpubkey
|
||||||
|
LOG.info("Expires at $expires")
|
||||||
|
val request = ObjectMessage(
|
||||||
|
stream = contact.stream,
|
||||||
|
expiresTime = expires,
|
||||||
|
payload = GetPubkey(contact)
|
||||||
|
)
|
||||||
|
proofOfWorkService.doProofOfWork(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryToFindMatchingPubkey(address: BitmessageAddress) {
|
||||||
|
addressRepository.getAddress(address.address)?.let {
|
||||||
|
address.alias = it.alias
|
||||||
|
address.isSubscribed = it.isSubscribed
|
||||||
|
}
|
||||||
|
for (objectMessage in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) {
|
||||||
|
try {
|
||||||
|
val pubkey = objectMessage.payload as Pubkey
|
||||||
|
if (address.version == 4L) {
|
||||||
|
val v4Pubkey = pubkey as V4Pubkey
|
||||||
|
if (Arrays.equals(address.tag, v4Pubkey.tag)) {
|
||||||
|
v4Pubkey.decrypt(address.publicDecryptionKey)
|
||||||
|
if (objectMessage.isSignatureValid(v4Pubkey)) {
|
||||||
|
address.pubkey = v4Pubkey
|
||||||
|
addressRepository.save(address)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
LOG.info("Found pubkey for $address but signature is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Arrays.equals(pubkey.ripe, address.ripe)) {
|
||||||
|
address.pubkey = pubkey
|
||||||
|
addressRepository.save(address)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.debug(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resendUnacknowledged() {
|
||||||
|
val messages = messageRepository.findMessagesToResend()
|
||||||
|
for (message in messages) {
|
||||||
|
send(message)
|
||||||
|
messageRepository.save(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextHolder {
|
||||||
|
fun setContext(context: InternalContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
|
||||||
|
|
||||||
|
@JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
|
||||||
|
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000
|
||||||
|
}
|
||||||
|
}
|
121
core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt
Normal file
121
core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
import ch.dissem.bitmessage.entity.*
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Msg
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class ProofOfWorkService : ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
|
||||||
|
|
||||||
|
private lateinit var ctx: InternalContext
|
||||||
|
private val cryptography by lazy { ctx.cryptography }
|
||||||
|
private val powRepo by lazy { ctx.proofOfWorkRepository }
|
||||||
|
private val messageRepo by lazy { ctx.messageRepository }
|
||||||
|
|
||||||
|
override fun setContext(context: InternalContext) {
|
||||||
|
ctx = context
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doMissingProofOfWork(delayInMilliseconds: Long) {
|
||||||
|
val items = powRepo.getItems()
|
||||||
|
if (items.isEmpty()) return
|
||||||
|
|
||||||
|
// Wait for 30 seconds, to let the application start up before putting heavy load on the CPU
|
||||||
|
Timer().schedule(object : TimerTask() {
|
||||||
|
override fun run() {
|
||||||
|
LOG.info("Doing POW for " + items.size + " tasks.")
|
||||||
|
for (initialHash in items) {
|
||||||
|
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
||||||
|
cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes,
|
||||||
|
this@ProofOfWorkService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, delayInMilliseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doProofOfWork(objectMessage: ObjectMessage) {
|
||||||
|
doProofOfWork(null, objectMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doProofOfWork(recipient: BitmessageAddress?, objectMessage: ObjectMessage) {
|
||||||
|
val pubkey = recipient?.pubkey
|
||||||
|
|
||||||
|
val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES
|
||||||
|
|
||||||
|
powRepo.putObject(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||||
|
if (objectMessage.payload is PlaintextHolder) {
|
||||||
|
objectMessage.payload.plaintext?.let {
|
||||||
|
it.initialHash = cryptography.getInitialHash(objectMessage)
|
||||||
|
messageRepo.save(it)
|
||||||
|
} ?: LOG.error("PlaintextHolder without Plaintext shouldn't make it to the POW")
|
||||||
|
}
|
||||||
|
cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) {
|
||||||
|
val ack = plaintext.ackMessage!!
|
||||||
|
messageRepo.save(plaintext)
|
||||||
|
val item = Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES,
|
||||||
|
expirationTime, plaintext)
|
||||||
|
powRepo.putObject(item)
|
||||||
|
cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||||
|
val (objectMessage, _, _, expirationTime, message) = powRepo.getItem(initialHash)
|
||||||
|
if (message == null) {
|
||||||
|
objectMessage.nonce = nonce
|
||||||
|
messageRepo.getMessage(initialHash)?.let {
|
||||||
|
it.inventoryVector = objectMessage.inventoryVector
|
||||||
|
it.updateNextTry()
|
||||||
|
ctx.labeler.markAsSent(it)
|
||||||
|
messageRepo.save(it)
|
||||||
|
}
|
||||||
|
ctx.inventory.storeObject(objectMessage)
|
||||||
|
ctx.networkHandler.offer(objectMessage.inventoryVector)
|
||||||
|
} else {
|
||||||
|
message.ackMessage!!.nonce = nonce
|
||||||
|
val newObjectMessage = ObjectMessage.Builder()
|
||||||
|
.stream(message.stream)
|
||||||
|
.expiresTime(expirationTime!!)
|
||||||
|
.payload(Msg(message))
|
||||||
|
.build()
|
||||||
|
if (newObjectMessage.isSigned) {
|
||||||
|
newObjectMessage.sign(message.from.privateKey!!)
|
||||||
|
}
|
||||||
|
if (newObjectMessage.payload is Encrypted) {
|
||||||
|
newObjectMessage.encrypt(message.to!!.pubkey!!)
|
||||||
|
}
|
||||||
|
doProofOfWork(message.to, newObjectMessage)
|
||||||
|
}
|
||||||
|
powRepo.removeObject(initialHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(ProofOfWorkService::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.constants
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some network constants
|
||||||
|
*/
|
||||||
|
object Network {
|
||||||
|
@JvmField val NETWORK_MAGIC_NUMBER = 8
|
||||||
|
@JvmField val HEADER_SIZE = 24
|
||||||
|
@JvmField val MAX_PAYLOAD_SIZE = 1600003
|
||||||
|
@JvmField val MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE
|
||||||
|
}
|
43
core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt
Normal file
43
core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'addr' command holds a list of known active Bitmessage nodes.
|
||||||
|
*/
|
||||||
|
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
|
||||||
|
override val command: MessagePayload.Command = MessagePayload.Command.ADDR
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.varInt(addresses.size, out)
|
||||||
|
for (address in addresses) {
|
||||||
|
address.write(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.varInt(addresses.size, buffer)
|
||||||
|
for (address in addresses) {
|
||||||
|
address.write(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||||
|
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.utils.AccessCounter
|
||||||
|
import ch.dissem.bitmessage.utils.Base58
|
||||||
|
import ch.dissem.bitmessage.utils.Bytes
|
||||||
|
import ch.dissem.bitmessage.utils.Decode.bytes
|
||||||
|
import ch.dissem.bitmessage.utils.Decode.varInt
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
|
||||||
|
* holding private keys.
|
||||||
|
*/
|
||||||
|
class BitmessageAddress : Serializable {
|
||||||
|
|
||||||
|
val version: Long
|
||||||
|
val stream: Long
|
||||||
|
val ripe: ByteArray
|
||||||
|
val tag: ByteArray?
|
||||||
|
/**
|
||||||
|
* The private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. It's easier to just create
|
||||||
|
* it regardless of address version.
|
||||||
|
*/
|
||||||
|
val publicDecryptionKey: ByteArray
|
||||||
|
|
||||||
|
val address: String
|
||||||
|
|
||||||
|
var privateKey: PrivateKey? = null
|
||||||
|
private set
|
||||||
|
var pubkey: Pubkey? = null
|
||||||
|
set(pubkey) {
|
||||||
|
if (pubkey != null) {
|
||||||
|
if (pubkey is V4Pubkey) {
|
||||||
|
if (!Arrays.equals(tag, pubkey.tag))
|
||||||
|
throw IllegalArgumentException("Pubkey has incompatible tag")
|
||||||
|
}
|
||||||
|
if (!Arrays.equals(ripe, pubkey.ripe))
|
||||||
|
throw IllegalArgumentException("Pubkey has incompatible ripe")
|
||||||
|
field = pubkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var alias: String? = null
|
||||||
|
var isSubscribed: Boolean = false
|
||||||
|
var isChan: Boolean = false
|
||||||
|
|
||||||
|
internal constructor(version: Long, stream: Long, ripe: ByteArray) {
|
||||||
|
this.version = version
|
||||||
|
this.stream = stream
|
||||||
|
this.ripe = ripe
|
||||||
|
|
||||||
|
val os = ByteArrayOutputStream()
|
||||||
|
Encode.varInt(version, os)
|
||||||
|
Encode.varInt(stream, os)
|
||||||
|
if (version < 4) {
|
||||||
|
val checksum = cryptography().sha512(os.toByteArray(), ripe)
|
||||||
|
this.tag = null
|
||||||
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||||
|
} else {
|
||||||
|
// for tag and decryption key, the checksum has to be created with 0x00 padding
|
||||||
|
val checksum = cryptography().doubleSha512(os.toByteArray(), ripe)
|
||||||
|
this.tag = Arrays.copyOfRange(checksum, 32, 64)
|
||||||
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||||
|
}
|
||||||
|
// but for the address and its checksum they need to be stripped
|
||||||
|
val offset = Bytes.numberOfLeadingZeros(ripe)
|
||||||
|
os.write(ripe, offset, ripe.size - offset)
|
||||||
|
val checksum = cryptography().doubleSha512(os.toByteArray())
|
||||||
|
os.write(checksum, 0, 4)
|
||||||
|
this.address = "BM-" + Base58.encode(os.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(publicKey: Pubkey) : this(publicKey.version, publicKey.stream, publicKey.ripe) {
|
||||||
|
this.pubkey = publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(address: String, passphrase: String) : this(address) {
|
||||||
|
val key = PrivateKey(this, passphrase)
|
||||||
|
if (!Arrays.equals(ripe, key.pubkey.ripe)) {
|
||||||
|
throw IllegalArgumentException("Wrong address or passphrase")
|
||||||
|
}
|
||||||
|
this.privateKey = key
|
||||||
|
this.pubkey = key.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(privateKey: PrivateKey) : this(privateKey.pubkey) {
|
||||||
|
this.privateKey = privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(address: String) {
|
||||||
|
this.address = address
|
||||||
|
val bytes = Base58.decode(address.substring(3))
|
||||||
|
val `in` = ByteArrayInputStream(bytes)
|
||||||
|
val counter = AccessCounter()
|
||||||
|
this.version = varInt(`in`, counter)
|
||||||
|
this.stream = varInt(`in`, counter)
|
||||||
|
this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20)
|
||||||
|
|
||||||
|
// test checksum
|
||||||
|
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
|
||||||
|
val expectedChecksum = bytes(`in`, 4)
|
||||||
|
for (i in 0..3) {
|
||||||
|
if (expectedChecksum[i] != checksum[i])
|
||||||
|
throw IllegalArgumentException("Checksum of address failed")
|
||||||
|
}
|
||||||
|
if (version < 4) {
|
||||||
|
checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe)
|
||||||
|
this.tag = null
|
||||||
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||||
|
} else {
|
||||||
|
checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe)
|
||||||
|
this.tag = Arrays.copyOfRange(checksum, 32, 64)
|
||||||
|
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return alias ?: address
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is BitmessageAddress) return false
|
||||||
|
return version == other.version &&
|
||||||
|
stream == other.stream &&
|
||||||
|
Arrays.equals(ripe, other.ripe)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Arrays.hashCode(ripe)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun has(feature: Feature?): Boolean {
|
||||||
|
return feature?.isActive(pubkey?.behaviorBitfield ?: 0) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun chan(address: String, passphrase: String): BitmessageAddress {
|
||||||
|
val result = BitmessageAddress(address, passphrase)
|
||||||
|
result.isChan = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun chan(stream: Long, passphrase: String): BitmessageAddress {
|
||||||
|
val privateKey = PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase)
|
||||||
|
val result = BitmessageAddress(privateKey)
|
||||||
|
result.isChan = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int,
|
||||||
|
version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
|
||||||
|
val result = ArrayList<BitmessageAddress>(numberOfAddresses)
|
||||||
|
val privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter)
|
||||||
|
for (pk in privateKeys) {
|
||||||
|
result.add(BitmessageAddress(pk))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun calculateTag(version: Long, stream: Long, ripe: ByteArray): ByteArray {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
Encode.varInt(version, out)
|
||||||
|
Encode.varInt(stream, out)
|
||||||
|
out.write(ripe)
|
||||||
|
return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
|
import ch.dissem.bitmessage.utils.AccessCounter
|
||||||
|
import ch.dissem.bitmessage.utils.Decode.bytes
|
||||||
|
import ch.dissem.bitmessage.utils.Decode.varString
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
open class CustomMessage(val customCommand: String, private val data: ByteArray? = null) : MessagePayload {
|
||||||
|
|
||||||
|
override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM
|
||||||
|
|
||||||
|
val isError: Boolean
|
||||||
|
|
||||||
|
fun getData(): ByteArray {
|
||||||
|
if (data != null) {
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
write(out)
|
||||||
|
return out.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
if (data != null) {
|
||||||
|
Encode.varString(customCommand, out)
|
||||||
|
out.write(data)
|
||||||
|
} else {
|
||||||
|
throw ApplicationException("Tried to write custom message without data. "
|
||||||
|
+ "Programmer: did you forget to override #write()?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
if (data != null) {
|
||||||
|
Encode.varString(customCommand, buffer)
|
||||||
|
buffer.put(data)
|
||||||
|
} else {
|
||||||
|
throw ApplicationException("Tried to write custom message without data. "
|
||||||
|
+ "Programmer: did you forget to override #write()?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val COMMAND_ERROR = "ERROR"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun read(`in`: InputStream, length: Int): CustomMessage {
|
||||||
|
val counter = AccessCounter()
|
||||||
|
return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun error(message: String): CustomMessage {
|
||||||
|
return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.isError = COMMAND_ERROR == customCommand
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,19 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for objects that have encrypted content
|
* Used for objects that have encrypted content
|
||||||
*/
|
*/
|
||||||
public interface Encrypted {
|
interface Encrypted {
|
||||||
void encrypt(byte[] publicKey) throws IOException;
|
fun encrypt(publicKey: ByteArray)
|
||||||
|
|
||||||
void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException;
|
@Throws(DecryptionFailedException::class)
|
||||||
|
fun decrypt(privateKey: ByteArray)
|
||||||
|
|
||||||
boolean isDecrypted();
|
val isDecrypted: Boolean
|
||||||
}
|
}
|
48
core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt
Normal file
48
core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'getdata' command is used to request objects from a node.
|
||||||
|
*/
|
||||||
|
class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload {
|
||||||
|
|
||||||
|
override val command: MessagePayload.Command = MessagePayload.Command.GETDATA
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.varInt(inventory.size, out)
|
||||||
|
for (iv in inventory) {
|
||||||
|
iv.write(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.varInt(inventory.size, buffer)
|
||||||
|
for (iv in inventory) {
|
||||||
|
iv.write(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val MAX_INVENTORY_SIZE = 50000
|
||||||
|
}
|
||||||
|
}
|
44
core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt
Normal file
44
core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
||||||
|
*/
|
||||||
|
class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload {
|
||||||
|
|
||||||
|
override val command: MessagePayload.Command = MessagePayload.Command.INV
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.varInt(inventory.size, out)
|
||||||
|
for (iv in inventory) {
|
||||||
|
iv.write(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.varInt(inventory.size, buffer)
|
||||||
|
for (iv in inventory) {
|
||||||
|
iv.write(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,15 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command can hold a network message payload
|
* A command can hold a network message payload
|
||||||
*/
|
*/
|
||||||
public interface MessagePayload extends Streamable {
|
interface MessagePayload : Streamable {
|
||||||
Command getCommand();
|
val command: Command
|
||||||
|
|
||||||
enum Command {
|
enum class Command {
|
||||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A network message is exchanged between two nodes.
|
||||||
|
*/
|
||||||
|
data class NetworkMessage(
|
||||||
|
/**
|
||||||
|
* The actual data, a message or an object. Not to be confused with objectPayload.
|
||||||
|
*/
|
||||||
|
val payload: MessagePayload
|
||||||
|
) : Streamable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First 4 bytes of sha512(payload)
|
||||||
|
*/
|
||||||
|
private fun getChecksum(bytes: ByteArray): ByteArray {
|
||||||
|
val d = cryptography().sha512(bytes)
|
||||||
|
return byteArrayOf(d[0], d[1], d[2], d[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
// magic
|
||||||
|
Encode.int32(MAGIC, out)
|
||||||
|
|
||||||
|
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||||
|
val command = payload.command.name.toLowerCase()
|
||||||
|
out.write(command.toByteArray(charset("ASCII")))
|
||||||
|
for (i in command.length..11) {
|
||||||
|
out.write(0x0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val payloadBytes = Encode.bytes(payload)
|
||||||
|
|
||||||
|
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||||
|
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||||
|
// larger than this.
|
||||||
|
Encode.int32(payloadBytes.size, out)
|
||||||
|
|
||||||
|
// checksum
|
||||||
|
out.write(getChecksum(payloadBytes))
|
||||||
|
|
||||||
|
// message payload
|
||||||
|
out.write(payloadBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A more efficient implementation of the write method, writing header data to the provided buffer and returning
|
||||||
|
* a new buffer containing the payload.
|
||||||
|
|
||||||
|
* @param headerBuffer where the header data is written to (24 bytes)
|
||||||
|
* *
|
||||||
|
* @return a buffer containing the payload, ready to be read.
|
||||||
|
*/
|
||||||
|
fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer {
|
||||||
|
return ByteBuffer.wrap(writeHeader(headerBuffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer]
|
||||||
|
* and write the header buffer as well as the returned payload buffer into the channel.
|
||||||
|
|
||||||
|
* @param buffer where everything gets written to. Needs to be large enough for the whole message
|
||||||
|
* * to be written.
|
||||||
|
*/
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
val payloadBytes = writeHeader(buffer)
|
||||||
|
buffer.put(payloadBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeHeader(out: ByteBuffer): ByteArray {
|
||||||
|
// magic
|
||||||
|
Encode.int32(MAGIC, out)
|
||||||
|
|
||||||
|
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||||
|
val command = payload.command.name.toLowerCase()
|
||||||
|
out.put(command.toByteArray(charset("ASCII")))
|
||||||
|
|
||||||
|
for (i in command.length..11) {
|
||||||
|
out.put(0.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
val payloadBytes = Encode.bytes(payload)
|
||||||
|
|
||||||
|
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||||
|
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||||
|
// larger than this.
|
||||||
|
Encode.int32(payloadBytes.size, out)
|
||||||
|
|
||||||
|
// checksum
|
||||||
|
out.put(getChecksum(payloadBytes))
|
||||||
|
|
||||||
|
// message payload
|
||||||
|
return payloadBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
|
||||||
|
*/
|
||||||
|
val MAGIC = 0xE9BEB4D9.toInt()
|
||||||
|
val MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectPayload
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.utils.Bytes
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'object' command sends an object that is shared throughout the network.
|
||||||
|
*/
|
||||||
|
data class ObjectMessage(
|
||||||
|
var nonce: ByteArray? = null,
|
||||||
|
val expiresTime: Long,
|
||||||
|
val payload: ObjectPayload,
|
||||||
|
val type: Long,
|
||||||
|
/**
|
||||||
|
* The object's version
|
||||||
|
*/
|
||||||
|
val version: Long,
|
||||||
|
val stream: Long
|
||||||
|
) : MessagePayload {
|
||||||
|
|
||||||
|
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
nonce: ByteArray? = null,
|
||||||
|
expiresTime: Long,
|
||||||
|
payload: ObjectPayload,
|
||||||
|
stream: Long
|
||||||
|
) : this(
|
||||||
|
nonce,
|
||||||
|
expiresTime,
|
||||||
|
payload,
|
||||||
|
payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
|
||||||
|
payload.version,
|
||||||
|
stream
|
||||||
|
)
|
||||||
|
|
||||||
|
val inventoryVector: InventoryVector
|
||||||
|
get() {
|
||||||
|
return InventoryVector(Bytes.truncate(cryptography().doubleSha512(
|
||||||
|
nonce ?: throw IllegalStateException("nonce must be set"),
|
||||||
|
payloadBytesWithoutNonce
|
||||||
|
), 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isEncrypted: Boolean
|
||||||
|
get() = payload is Encrypted && !payload.isDecrypted
|
||||||
|
|
||||||
|
val isSigned: Boolean
|
||||||
|
get() = payload.isSigned
|
||||||
|
|
||||||
|
private val bytesToSign: ByteArray
|
||||||
|
get() {
|
||||||
|
try {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
writeHeaderWithoutNonce(out)
|
||||||
|
payload.writeBytesToSign(out)
|
||||||
|
return out.toByteArray()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw ApplicationException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sign(key: PrivateKey) {
|
||||||
|
if (payload.isSigned) {
|
||||||
|
payload.signature = cryptography().getSignature(bytesToSign, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(DecryptionFailedException::class)
|
||||||
|
fun decrypt(key: PrivateKey) {
|
||||||
|
if (payload is Encrypted) {
|
||||||
|
payload.decrypt(key.privateEncryptionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(DecryptionFailedException::class)
|
||||||
|
fun decrypt(privateEncryptionKey: ByteArray) {
|
||||||
|
if (payload is Encrypted) {
|
||||||
|
payload.decrypt(privateEncryptionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encrypt(publicEncryptionKey: ByteArray) {
|
||||||
|
if (payload is Encrypted) {
|
||||||
|
payload.encrypt(publicEncryptionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encrypt(publicKey: Pubkey) {
|
||||||
|
try {
|
||||||
|
if (payload is Encrypted) {
|
||||||
|
payload.encrypt(publicKey.encryptionKey)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw ApplicationException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSignatureValid(pubkey: Pubkey): Boolean {
|
||||||
|
if (isEncrypted) throw IllegalStateException("Payload must be decrypted first")
|
||||||
|
return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(nonce ?: ByteArray(8))
|
||||||
|
out.write(payloadBytesWithoutNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(nonce ?: ByteArray(8))
|
||||||
|
buffer.put(payloadBytesWithoutNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeHeaderWithoutNonce(out: OutputStream) {
|
||||||
|
Encode.int64(expiresTime, out)
|
||||||
|
Encode.int32(type, out)
|
||||||
|
Encode.varInt(version, out)
|
||||||
|
Encode.varInt(stream, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
val payloadBytesWithoutNonce: ByteArray by lazy {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
writeHeaderWithoutNonce(out)
|
||||||
|
payload.write(out)
|
||||||
|
out.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
private var nonce: ByteArray? = null
|
||||||
|
private var expiresTime: Long = 0
|
||||||
|
private var objectType: Long? = null
|
||||||
|
private var streamNumber: Long = 0
|
||||||
|
private var payload: ObjectPayload? = null
|
||||||
|
|
||||||
|
fun nonce(nonce: ByteArray): Builder {
|
||||||
|
this.nonce = nonce
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expiresTime(expiresTime: Long): Builder {
|
||||||
|
this.expiresTime = expiresTime
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun objectType(objectType: Long): Builder {
|
||||||
|
this.objectType = objectType
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun objectType(objectType: ObjectType): Builder {
|
||||||
|
this.objectType = objectType.number
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stream(streamNumber: Long): Builder {
|
||||||
|
this.streamNumber = streamNumber
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun payload(payload: ObjectPayload): Builder {
|
||||||
|
this.payload = payload
|
||||||
|
if (this.objectType == null)
|
||||||
|
this.objectType = payload.type?.number
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): ObjectMessage {
|
||||||
|
return ObjectMessage(
|
||||||
|
nonce = nonce,
|
||||||
|
expiresTime = expiresTime,
|
||||||
|
type = objectType!!,
|
||||||
|
version = payload!!.version,
|
||||||
|
stream = if (streamNumber > 0) streamNumber else payload!!.stream,
|
||||||
|
payload = payload!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is ObjectMessage) return false
|
||||||
|
return expiresTime == other.expiresTime &&
|
||||||
|
type == other.type &&
|
||||||
|
version == other.version &&
|
||||||
|
stream == other.stream &&
|
||||||
|
payload == other.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = Arrays.hashCode(nonce)
|
||||||
|
result = 31 * result + (expiresTime xor expiresTime.ushr(32)).toInt()
|
||||||
|
result = 31 * result + (type xor type.ushr(32)).toInt()
|
||||||
|
result = 31 * result + (version xor version.ushr(32)).toInt()
|
||||||
|
result = 31 * result + (stream xor stream.ushr(32)).toInt()
|
||||||
|
result = 31 * result + (payload.hashCode())
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
768
core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt
Normal file
768
core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt
Normal file
@ -0,0 +1,768 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Encoding.*
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Msg
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Attachment
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
|
import ch.dissem.bitmessage.factory.ExtendedEncodingFactory
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.utils.*
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import java.io.*
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
import java.util.Collections
|
||||||
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
|
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
|
||||||
|
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
|
||||||
|
EXTENDED -> Message.Builder().subject(subject).body(body).build().zip()
|
||||||
|
TRIVIAL -> (subject + body).toByteArray()
|
||||||
|
IGNORE -> ByteArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? {
|
||||||
|
if (ackData != null) {
|
||||||
|
return ackData
|
||||||
|
} else if (type == MSG) {
|
||||||
|
return cryptography().randomBytes(Msg.ACK_LENGTH)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plaintext message before encryption or after decryption.
|
||||||
|
*/
|
||||||
|
class Plaintext private constructor(
|
||||||
|
val type: Type,
|
||||||
|
val from: BitmessageAddress,
|
||||||
|
to: BitmessageAddress?,
|
||||||
|
val encodingCode: Long,
|
||||||
|
val message: ByteArray,
|
||||||
|
val ackData: ByteArray?,
|
||||||
|
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
|
||||||
|
val conversationId: UUID = UUID.randomUUID(),
|
||||||
|
var inventoryVector: InventoryVector? = null,
|
||||||
|
var signature: ByteArray? = null,
|
||||||
|
sent: Long? = null,
|
||||||
|
val received: Long? = null,
|
||||||
|
var initialHash: ByteArray? = null,
|
||||||
|
ttl: Long = TTL.msg,
|
||||||
|
val labels: MutableSet<Label> = HashSet(),
|
||||||
|
status: Status
|
||||||
|
) : Streamable {
|
||||||
|
|
||||||
|
var id: Any? = null
|
||||||
|
set(id) {
|
||||||
|
if (this.id != null) throw IllegalStateException("ID already set")
|
||||||
|
field = id
|
||||||
|
}
|
||||||
|
|
||||||
|
var to: BitmessageAddress? = to
|
||||||
|
set(to) {
|
||||||
|
if (to == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.to != null) {
|
||||||
|
if (this.to!!.version != 0L)
|
||||||
|
throw IllegalStateException("Correct address already set")
|
||||||
|
if (!Arrays.equals(this.to!!.ripe, to.ripe)) {
|
||||||
|
throw IllegalArgumentException("RIPEs don't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field = to
|
||||||
|
}
|
||||||
|
|
||||||
|
val stream: Long
|
||||||
|
get() = to?.stream ?: from.stream
|
||||||
|
|
||||||
|
val extendedData: ExtendedEncoding? by lazy {
|
||||||
|
if (encodingCode == EXTENDED.code) {
|
||||||
|
ExtendedEncodingFactory.unzip(message)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val ackMessage: ObjectMessage? by ackMessage
|
||||||
|
|
||||||
|
var status: Status = status
|
||||||
|
set(status) {
|
||||||
|
if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) {
|
||||||
|
sent = UnixTime.now
|
||||||
|
}
|
||||||
|
field = status
|
||||||
|
}
|
||||||
|
|
||||||
|
val encoding: Encoding? by lazy { Encoding.fromCode(encodingCode) }
|
||||||
|
var sent: Long? = sent
|
||||||
|
private set
|
||||||
|
var retries: Int = 0
|
||||||
|
private set
|
||||||
|
var nextTry: Long? = null
|
||||||
|
private set
|
||||||
|
val ttl: Long = ttl
|
||||||
|
@JvmName("getTTL") get
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
type: Type,
|
||||||
|
from: BitmessageAddress,
|
||||||
|
to: BitmessageAddress?,
|
||||||
|
encoding: Encoding,
|
||||||
|
message: ByteArray,
|
||||||
|
ackData: ByteArray? = null,
|
||||||
|
conversationId: UUID = UUID.randomUUID(),
|
||||||
|
inventoryVector: InventoryVector? = null,
|
||||||
|
signature: ByteArray? = null,
|
||||||
|
received: Long? = null,
|
||||||
|
initialHash: ByteArray? = null,
|
||||||
|
ttl: Long = TTL.msg,
|
||||||
|
labels: MutableSet<Label> = HashSet(),
|
||||||
|
status: Status
|
||||||
|
) : this(
|
||||||
|
type = type,
|
||||||
|
from = from,
|
||||||
|
to = to,
|
||||||
|
encodingCode = encoding.code,
|
||||||
|
message = message,
|
||||||
|
ackData = ackData(type, ackData),
|
||||||
|
conversationId = conversationId,
|
||||||
|
inventoryVector = inventoryVector,
|
||||||
|
signature = signature,
|
||||||
|
received = received,
|
||||||
|
initialHash = initialHash,
|
||||||
|
ttl = ttl,
|
||||||
|
labels = labels,
|
||||||
|
status = status
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
type: Type,
|
||||||
|
from: BitmessageAddress,
|
||||||
|
to: BitmessageAddress?,
|
||||||
|
encoding: Long,
|
||||||
|
message: ByteArray,
|
||||||
|
ackMessage: ByteArray?,
|
||||||
|
conversationId: UUID = UUID.randomUUID(),
|
||||||
|
inventoryVector: InventoryVector? = null,
|
||||||
|
signature: ByteArray? = null,
|
||||||
|
received: Long? = null,
|
||||||
|
initialHash: ByteArray? = null,
|
||||||
|
ttl: Long = TTL.msg,
|
||||||
|
labels: MutableSet<Label> = HashSet(),
|
||||||
|
status: Status
|
||||||
|
) : this(
|
||||||
|
type = type,
|
||||||
|
from = from,
|
||||||
|
to = to,
|
||||||
|
encodingCode = encoding,
|
||||||
|
message = message,
|
||||||
|
ackData = null,
|
||||||
|
ackMessage = lazy {
|
||||||
|
if (ackMessage != null && ackMessage.isNotEmpty()) {
|
||||||
|
Factory.getObjectMessage(
|
||||||
|
3,
|
||||||
|
ByteArrayInputStream(ackMessage),
|
||||||
|
ackMessage.size)
|
||||||
|
} else null
|
||||||
|
},
|
||||||
|
conversationId = conversationId,
|
||||||
|
inventoryVector = inventoryVector,
|
||||||
|
signature = signature,
|
||||||
|
received = received,
|
||||||
|
initialHash = initialHash,
|
||||||
|
ttl = ttl,
|
||||||
|
labels = labels,
|
||||||
|
status = status
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
type: Type,
|
||||||
|
from: BitmessageAddress,
|
||||||
|
to: BitmessageAddress? = null,
|
||||||
|
encoding: Encoding = SIMPLE,
|
||||||
|
subject: String,
|
||||||
|
body: String,
|
||||||
|
ackData: ByteArray? = null,
|
||||||
|
conversationId: UUID = UUID.randomUUID(),
|
||||||
|
ttl: Long = TTL.msg,
|
||||||
|
labels: MutableSet<Label> = HashSet(),
|
||||||
|
status: Status = Status.DRAFT
|
||||||
|
) : this(
|
||||||
|
type = type,
|
||||||
|
from = from,
|
||||||
|
to = to,
|
||||||
|
encoding = encoding,
|
||||||
|
message = message(encoding, subject, body),
|
||||||
|
ackData = ackData(type, ackData),
|
||||||
|
conversationId = conversationId,
|
||||||
|
inventoryVector = null,
|
||||||
|
signature = null,
|
||||||
|
received = null,
|
||||||
|
initialHash = null,
|
||||||
|
ttl = ttl,
|
||||||
|
labels = labels,
|
||||||
|
status = status
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(builder: Builder) : this(
|
||||||
|
// Calling prepare() here is somewhat ugly, but also a foolproof way to make sure the builder is properly initialized
|
||||||
|
type = builder.prepare().type,
|
||||||
|
from = builder.from ?: throw IllegalStateException("sender identity not set"),
|
||||||
|
to = builder.to,
|
||||||
|
encodingCode = builder.encoding,
|
||||||
|
message = builder.message,
|
||||||
|
ackData = builder.ackData,
|
||||||
|
ackMessage = lazy {
|
||||||
|
val ackMsg = builder.ackMessage
|
||||||
|
if (ackMsg != null && ackMsg.isNotEmpty()) {
|
||||||
|
Factory.getObjectMessage(
|
||||||
|
3,
|
||||||
|
ByteArrayInputStream(ackMsg),
|
||||||
|
ackMsg.size)
|
||||||
|
} else {
|
||||||
|
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
conversationId = builder.conversation ?: UUID.randomUUID(),
|
||||||
|
inventoryVector = builder.inventoryVector,
|
||||||
|
signature = builder.signature,
|
||||||
|
sent = builder.sent,
|
||||||
|
received = builder.received,
|
||||||
|
initialHash = null,
|
||||||
|
ttl = builder.ttl,
|
||||||
|
labels = builder.labels,
|
||||||
|
status = builder.status ?: Status.RECEIVED
|
||||||
|
) {
|
||||||
|
id = builder.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(out: OutputStream, includeSignature: Boolean) {
|
||||||
|
Encode.varInt(from.version, out)
|
||||||
|
Encode.varInt(from.stream, out)
|
||||||
|
from.pubkey?.apply {
|
||||||
|
Encode.int32(behaviorBitfield, out)
|
||||||
|
out.write(signingKey, 1, 64)
|
||||||
|
out.write(encryptionKey, 1, 64)
|
||||||
|
if (from.version >= 3) {
|
||||||
|
Encode.varInt(nonceTrialsPerByte, out)
|
||||||
|
Encode.varInt(extraBytes, out)
|
||||||
|
}
|
||||||
|
} ?: {
|
||||||
|
Encode.int32(0, out)
|
||||||
|
val empty = ByteArray(64)
|
||||||
|
out.write(empty)
|
||||||
|
out.write(empty)
|
||||||
|
if (from.version >= 3) {
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
}
|
||||||
|
}.invoke()
|
||||||
|
if (type == MSG) {
|
||||||
|
out.write(to?.ripe ?: throw IllegalStateException("No recipient set for message"))
|
||||||
|
}
|
||||||
|
Encode.varInt(encodingCode, out)
|
||||||
|
Encode.varInt(message.size, out)
|
||||||
|
out.write(message)
|
||||||
|
if (type == MSG) {
|
||||||
|
if (to?.has(Feature.DOES_ACK) ?: false) {
|
||||||
|
val ack = ByteArrayOutputStream()
|
||||||
|
ackMessage?.write(ack)
|
||||||
|
Encode.varBytes(ack.toByteArray(), out)
|
||||||
|
} else {
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (includeSignature) {
|
||||||
|
if (signature == null) {
|
||||||
|
Encode.varInt(0, out)
|
||||||
|
} else {
|
||||||
|
Encode.varBytes(signature!!, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(buffer: ByteBuffer, includeSignature: Boolean) {
|
||||||
|
Encode.varInt(from.version, buffer)
|
||||||
|
Encode.varInt(from.stream, buffer)
|
||||||
|
if (from.pubkey == null) {
|
||||||
|
Encode.int32(0, buffer)
|
||||||
|
val empty = ByteArray(64)
|
||||||
|
buffer.put(empty)
|
||||||
|
buffer.put(empty)
|
||||||
|
if (from.version >= 3) {
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Encode.int32(from.pubkey!!.behaviorBitfield, buffer)
|
||||||
|
buffer.put(from.pubkey!!.signingKey, 1, 64)
|
||||||
|
buffer.put(from.pubkey!!.encryptionKey, 1, 64)
|
||||||
|
if (from.version >= 3) {
|
||||||
|
Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer)
|
||||||
|
Encode.varInt(from.pubkey!!.extraBytes, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == MSG) {
|
||||||
|
buffer.put(to!!.ripe)
|
||||||
|
}
|
||||||
|
Encode.varInt(encodingCode, buffer)
|
||||||
|
Encode.varBytes(message, buffer)
|
||||||
|
if (type == MSG) {
|
||||||
|
if (to!!.has(Feature.DOES_ACK) && ackMessage != null) {
|
||||||
|
Encode.varBytes(Encode.bytes(ackMessage!!), buffer)
|
||||||
|
} else {
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (includeSignature) {
|
||||||
|
val sig = signature
|
||||||
|
if (sig == null) {
|
||||||
|
Encode.varInt(0, buffer)
|
||||||
|
} else {
|
||||||
|
Encode.varBytes(sig, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
write(out, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
write(buffer, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateNextTry() {
|
||||||
|
if (to != null) {
|
||||||
|
if (nextTry == null) {
|
||||||
|
if (sent != null && to!!.has(Feature.DOES_ACK)) {
|
||||||
|
nextTry = UnixTime.now + ttl
|
||||||
|
retries++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextTry = nextTry!! + (1 shl retries) * ttl
|
||||||
|
retries++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val subject: String?
|
||||||
|
get() {
|
||||||
|
val s = Scanner(ByteArrayInputStream(message), "UTF-8")
|
||||||
|
val firstLine = s.nextLine()
|
||||||
|
if (encodingCode == EXTENDED.code) {
|
||||||
|
if (Message.TYPE == extendedData?.type) {
|
||||||
|
return (extendedData!!.content as? Message)?.subject
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else if (encodingCode == SIMPLE.code) {
|
||||||
|
return firstLine.substring("Subject:".length).trim { it <= ' ' }
|
||||||
|
} else if (firstLine.length > 50) {
|
||||||
|
return firstLine.substring(0, 50).trim { it <= ' ' } + "..."
|
||||||
|
} else {
|
||||||
|
return firstLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val text: String?
|
||||||
|
get() {
|
||||||
|
if (encodingCode == EXTENDED.code) {
|
||||||
|
if (Message.TYPE == extendedData?.type) {
|
||||||
|
return (extendedData?.content as Message?)?.body
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val text = String(message)
|
||||||
|
if (encodingCode == SIMPLE.code) {
|
||||||
|
return text.substring(text.indexOf("\nBody:") + 6)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : ExtendedEncoding.ExtendedType> getExtendedData(type: Class<T>): T? {
|
||||||
|
val extendedData = extendedData ?: return null
|
||||||
|
if (type.isInstance(extendedData.content)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return extendedData.content as T
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val parents: List<InventoryVector>
|
||||||
|
get() {
|
||||||
|
val extendedData = extendedData ?: return emptyList()
|
||||||
|
if (Message.TYPE == extendedData.type) {
|
||||||
|
return (extendedData.content as Message).parents
|
||||||
|
} else {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val files: List<Attachment>
|
||||||
|
get() {
|
||||||
|
val extendedData = extendedData ?: return emptyList()
|
||||||
|
if (Message.TYPE == extendedData.type) {
|
||||||
|
return (extendedData.content as Message).files
|
||||||
|
} else {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Plaintext) return false
|
||||||
|
return encoding == other.encoding &&
|
||||||
|
from.address == other.from.address &&
|
||||||
|
Arrays.equals(message, other.message) &&
|
||||||
|
ackMessage == other.ackMessage &&
|
||||||
|
Arrays.equals(to?.ripe, other.to?.ripe) &&
|
||||||
|
Arrays.equals(signature, other.signature) &&
|
||||||
|
status == other.status &&
|
||||||
|
sent == other.sent &&
|
||||||
|
received == other.received &&
|
||||||
|
labels == other.labels
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addLabels(vararg labels: Label) {
|
||||||
|
Collections.addAll(this.labels, *labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addLabels(labels: Collection<Label>?) {
|
||||||
|
if (labels != null) {
|
||||||
|
this.labels.addAll(labels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeLabel(type: Label.Type) {
|
||||||
|
labels.removeAll { it.type == type }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isUnread(): Boolean {
|
||||||
|
return labels.any { it.type == Label.Type.UNREAD }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val subject = subject
|
||||||
|
if (subject?.isNotEmpty() ?: false) {
|
||||||
|
return subject!!
|
||||||
|
} else {
|
||||||
|
return Strings.hex(
|
||||||
|
initialHash ?: return super.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Encoding constructor(code: Long) {
|
||||||
|
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
|
||||||
|
|
||||||
|
var code: Long = 0
|
||||||
|
internal set
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic fun fromCode(code: Long): Encoding? {
|
||||||
|
for (e in values()) {
|
||||||
|
if (e.code == code) {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
DRAFT,
|
||||||
|
// For sent messages
|
||||||
|
PUBKEY_REQUESTED,
|
||||||
|
DOING_PROOF_OF_WORK,
|
||||||
|
SENT,
|
||||||
|
SENT_ACKNOWLEDGED,
|
||||||
|
RECEIVED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
MSG, BROADCAST
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder(internal val type: Type) {
|
||||||
|
internal var id: Any? = null
|
||||||
|
internal var inventoryVector: InventoryVector? = null
|
||||||
|
internal var from: BitmessageAddress? = null
|
||||||
|
internal var to: BitmessageAddress? = null
|
||||||
|
private var addressVersion: Long = 0
|
||||||
|
private var stream: Long = 0
|
||||||
|
private var behaviorBitfield: Int = 0
|
||||||
|
private var publicSigningKey: ByteArray? = null
|
||||||
|
private var publicEncryptionKey: ByteArray? = null
|
||||||
|
private var nonceTrialsPerByte: Long = 0
|
||||||
|
private var extraBytes: Long = 0
|
||||||
|
private var destinationRipe: ByteArray? = null
|
||||||
|
private var preventAck: Boolean = false
|
||||||
|
internal var encoding: Long = 0
|
||||||
|
internal var message = ByteArray(0)
|
||||||
|
internal var ackData: ByteArray? = null
|
||||||
|
internal var ackMessage: ByteArray? = null
|
||||||
|
internal var signature: ByteArray? = null
|
||||||
|
internal var sent: Long? = null
|
||||||
|
internal var received: Long? = null
|
||||||
|
internal var status: Status? = null
|
||||||
|
internal val labels = LinkedHashSet<Label>()
|
||||||
|
internal var ttl: Long = 0
|
||||||
|
internal var retries: Int = 0
|
||||||
|
internal var nextTry: Long? = null
|
||||||
|
internal var conversation: UUID? = null
|
||||||
|
|
||||||
|
fun id(id: Any): Builder {
|
||||||
|
this.id = id
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IV(iv: InventoryVector?): Builder {
|
||||||
|
this.inventoryVector = iv
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun from(address: BitmessageAddress): Builder {
|
||||||
|
from = address
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun to(address: BitmessageAddress?): Builder {
|
||||||
|
if (address != null) {
|
||||||
|
if (type != MSG && to != null)
|
||||||
|
throw IllegalArgumentException("recipient address only allowed for msg")
|
||||||
|
to = address
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addressVersion(addressVersion: Long): Builder {
|
||||||
|
this.addressVersion = addressVersion
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stream(stream: Long): Builder {
|
||||||
|
this.stream = stream
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun behaviorBitfield(behaviorBitfield: Int): Builder {
|
||||||
|
this.behaviorBitfield = behaviorBitfield
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
|
||||||
|
this.publicSigningKey = publicSigningKey
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
|
||||||
|
this.publicEncryptionKey = publicEncryptionKey
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
|
||||||
|
this.nonceTrialsPerByte = nonceTrialsPerByte
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extraBytes(extraBytes: Long): Builder {
|
||||||
|
this.extraBytes = extraBytes
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destinationRipe(ripe: ByteArray?): Builder {
|
||||||
|
if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg")
|
||||||
|
this.destinationRipe = ripe
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun preventAck(preventAck: Boolean = true): Builder {
|
||||||
|
this.preventAck = preventAck
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encoding(encoding: Encoding): Builder {
|
||||||
|
this.encoding = encoding.code
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encoding(encoding: Long): Builder {
|
||||||
|
this.encoding = encoding
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun message(message: ExtendedEncoding): Builder {
|
||||||
|
this.encoding = EXTENDED.code
|
||||||
|
this.message = message.zip()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun message(subject: String, message: String): Builder {
|
||||||
|
try {
|
||||||
|
this.encoding = SIMPLE.code
|
||||||
|
this.message = "Subject:$subject\nBody:$message".toByteArray(charset("UTF-8"))
|
||||||
|
} catch (e: UnsupportedEncodingException) {
|
||||||
|
throw ApplicationException(e)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun message(message: ByteArray): Builder {
|
||||||
|
this.message = message
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ackMessage(ack: ByteArray?): Builder {
|
||||||
|
if (type != MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg")
|
||||||
|
this.ackMessage = ack
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ackData(ackData: ByteArray?): Builder {
|
||||||
|
if (type != MSG && ackData != null)
|
||||||
|
throw IllegalArgumentException("ackMessage only allowed for msg")
|
||||||
|
this.ackData = ackData
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signature(signature: ByteArray?): Builder {
|
||||||
|
this.signature = signature
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sent(sent: Long?): Builder {
|
||||||
|
this.sent = sent
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun received(received: Long?): Builder {
|
||||||
|
this.received = received
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun status(status: Status): Builder {
|
||||||
|
this.status = status
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun labels(labels: Collection<Label>): Builder {
|
||||||
|
this.labels.addAll(labels)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ttl(ttl: Long): Builder {
|
||||||
|
this.ttl = ttl
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retries(retries: Int): Builder {
|
||||||
|
this.retries = retries
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextTry(nextTry: Long?): Builder {
|
||||||
|
this.nextTry = nextTry
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun conversation(id: UUID): Builder {
|
||||||
|
this.conversation = id
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun prepare(): Builder {
|
||||||
|
if (from == null) {
|
||||||
|
from = BitmessageAddress(Factory.createPubkey(
|
||||||
|
addressVersion,
|
||||||
|
stream,
|
||||||
|
publicSigningKey!!,
|
||||||
|
publicEncryptionKey!!,
|
||||||
|
nonceTrialsPerByte,
|
||||||
|
extraBytes,
|
||||||
|
behaviorBitfield
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
|
||||||
|
to = BitmessageAddress(0, 0, destinationRipe!!)
|
||||||
|
}
|
||||||
|
if (preventAck) {
|
||||||
|
ackData = null
|
||||||
|
ackMessage = null
|
||||||
|
} else if (type == MSG && ackMessage == null && ackData == null) {
|
||||||
|
ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
|
||||||
|
}
|
||||||
|
if (ttl <= 0) {
|
||||||
|
ttl = TTL.msg
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Plaintext {
|
||||||
|
return Plaintext(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmStatic fun read(type: Type, `in`: InputStream): Plaintext {
|
||||||
|
return readWithoutSignature(type, `in`)
|
||||||
|
.signature(Decode.varBytes(`in`))
|
||||||
|
.received(UnixTime.now)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder {
|
||||||
|
val version = Decode.varInt(`in`)
|
||||||
|
return Builder(type)
|
||||||
|
.addressVersion(version)
|
||||||
|
.stream(Decode.varInt(`in`))
|
||||||
|
.behaviorBitfield(Decode.int32(`in`))
|
||||||
|
.publicSigningKey(Decode.bytes(`in`, 64))
|
||||||
|
.publicEncryptionKey(Decode.bytes(`in`, 64))
|
||||||
|
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0)
|
||||||
|
.extraBytes(if (version >= 3) Decode.varInt(`in`) else 0)
|
||||||
|
.destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null)
|
||||||
|
.encoding(Decode.varInt(`in`))
|
||||||
|
.message(Decode.varBytes(`in`))
|
||||||
|
.ackMessage(if (type == MSG) Decode.varBytes(`in`) else null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,7 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
public class DecryptionFailedException extends Exception {
|
interface PlaintextHolder {
|
||||||
|
val plaintext: Plaintext?
|
||||||
}
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that can be written to an [OutputStream]
|
||||||
|
*/
|
||||||
|
interface Streamable : Serializable {
|
||||||
|
fun write(out: OutputStream)
|
||||||
|
|
||||||
|
fun write(buffer: ByteBuffer)
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,22 +14,27 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity;
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.OutputStream
|
||||||
import java.io.OutputStream;
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
||||||
*/
|
*/
|
||||||
public class VerAck implements MessagePayload {
|
class VerAck : MessagePayload {
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.VERACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
// 'verack' doesn't have any payload, so there is nothing to write
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
// 'verack' doesn't have any payload, so there is nothing to write
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val serialVersionUID = -4302074845199181687L
|
||||||
|
}
|
||||||
}
|
}
|
204
core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt
Normal file
204
core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
||||||
|
*/
|
||||||
|
class Version constructor(
|
||||||
|
/**
|
||||||
|
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
|
||||||
|
* version is lower but continue with the connection if it is higher.
|
||||||
|
*/
|
||||||
|
val version: Int = BitmessageContext.CURRENT_VERSION,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bitfield of features to be enabled for this connection
|
||||||
|
*/
|
||||||
|
val services: Long = Version.Service.getServiceFlag(Version.Service.NODE_NETWORK),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* standard UNIX timestamp in seconds
|
||||||
|
*/
|
||||||
|
val timestamp: Long = UnixTime.now,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The network address of the node receiving this message (not including the time or stream number)
|
||||||
|
*/
|
||||||
|
val addrRecv: NetworkAddress,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The network address of the node emitting this message (not including the time or stream number and the ip itself
|
||||||
|
* is ignored by the receiver)
|
||||||
|
*/
|
||||||
|
val addrFrom: NetworkAddress,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Random nonce used to detect connections to self.
|
||||||
|
*/
|
||||||
|
val nonce: Long,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes.
|
||||||
|
*/
|
||||||
|
val userAgent: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000
|
||||||
|
* stream numbers.
|
||||||
|
*/
|
||||||
|
val streams: LongArray = longArrayOf(1)
|
||||||
|
) : MessagePayload {
|
||||||
|
|
||||||
|
fun provides(service: Service?): Boolean {
|
||||||
|
return service != null && service.isEnabled(services)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val command: MessagePayload.Command = MessagePayload.Command.VERSION
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.int32(version, out)
|
||||||
|
Encode.int64(services, out)
|
||||||
|
Encode.int64(timestamp, out)
|
||||||
|
addrRecv.write(out, true)
|
||||||
|
addrFrom.write(out, true)
|
||||||
|
Encode.int64(nonce, out)
|
||||||
|
Encode.varString(userAgent, out)
|
||||||
|
Encode.varIntList(streams, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.int32(version, buffer)
|
||||||
|
Encode.int64(services, buffer)
|
||||||
|
Encode.int64(timestamp, buffer)
|
||||||
|
addrRecv.write(buffer, true)
|
||||||
|
addrFrom.write(buffer, true)
|
||||||
|
Encode.int64(nonce, buffer)
|
||||||
|
Encode.varString(userAgent, buffer)
|
||||||
|
Encode.varIntList(streams, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
private var version: Int = 0
|
||||||
|
private var services: Long = 0
|
||||||
|
private var timestamp: Long = 0
|
||||||
|
private var addrRecv: NetworkAddress? = null
|
||||||
|
private var addrFrom: NetworkAddress? = null
|
||||||
|
private var nonce: Long = 0
|
||||||
|
private var userAgent: String? = null
|
||||||
|
private var streamNumbers: LongArray? = null
|
||||||
|
|
||||||
|
fun defaults(clientNonce: Long): Builder {
|
||||||
|
version = BitmessageContext.CURRENT_VERSION
|
||||||
|
services = Service.getServiceFlag(Service.NODE_NETWORK)
|
||||||
|
timestamp = UnixTime.now
|
||||||
|
userAgent = "/Jabit:0.0.1/"
|
||||||
|
streamNumbers = longArrayOf(1)
|
||||||
|
nonce = clientNonce
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun version(version: Int): Builder {
|
||||||
|
this.version = version
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun services(vararg services: Service): Builder {
|
||||||
|
this.services = Service.getServiceFlag(*services)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun services(services: Long): Builder {
|
||||||
|
this.services = services
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun timestamp(timestamp: Long): Builder {
|
||||||
|
this.timestamp = timestamp
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addrRecv(addrRecv: NetworkAddress): Builder {
|
||||||
|
this.addrRecv = addrRecv
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addrFrom(addrFrom: NetworkAddress): Builder {
|
||||||
|
this.addrFrom = addrFrom
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nonce(nonce: Long): Builder {
|
||||||
|
this.nonce = nonce
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun userAgent(userAgent: String): Builder {
|
||||||
|
this.userAgent = userAgent
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun streams(vararg streamNumbers: Long): Builder {
|
||||||
|
this.streamNumbers = streamNumbers
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Version {
|
||||||
|
val addrRecv = this.addrRecv
|
||||||
|
val addrFrom = this.addrFrom
|
||||||
|
if (addrRecv == null || addrFrom == null) {
|
||||||
|
throw IllegalStateException("Receiving and sending address must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Version(
|
||||||
|
version = version,
|
||||||
|
services = services,
|
||||||
|
timestamp = timestamp,
|
||||||
|
addrRecv = addrRecv, addrFrom = addrFrom,
|
||||||
|
nonce = nonce,
|
||||||
|
userAgent = userAgent ?: "/Jabit:0.0.1/",
|
||||||
|
streams = streamNumbers ?: longArrayOf(1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Service constructor(internal var flag: Long) {
|
||||||
|
// TODO: NODE_SSL(2);
|
||||||
|
NODE_NETWORK(1);
|
||||||
|
|
||||||
|
fun isEnabled(flag: Long): Boolean {
|
||||||
|
return (flag and this.flag) != 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getServiceFlag(vararg services: Service): Long {
|
||||||
|
var flag: Long = 0
|
||||||
|
for (service in services) {
|
||||||
|
flag = flag or service.flag
|
||||||
|
}
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Encrypted
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||||
|
import ch.dissem.bitmessage.entity.PlaintextHolder
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||||
|
* Broadcasts are version 4 or 5.
|
||||||
|
*/
|
||||||
|
abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder {
|
||||||
|
|
||||||
|
override val isSigned: Boolean = true
|
||||||
|
|
||||||
|
override var signature: ByteArray?
|
||||||
|
get() = plaintext?.signature
|
||||||
|
set(signature) {
|
||||||
|
plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encrypt(publicKey: ByteArray) {
|
||||||
|
this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encrypt() {
|
||||||
|
encrypt(cryptography().createPublicKey(plaintext?.from?.publicDecryptionKey ?: return))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(DecryptionFailedException::class)
|
||||||
|
override fun decrypt(privateKey: ByteArray) {
|
||||||
|
plaintext = Plaintext.read(BROADCAST, encrypted?.decrypt(privateKey) ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(DecryptionFailedException::class)
|
||||||
|
fun decrypt(address: BitmessageAddress) {
|
||||||
|
decrypt(address.publicDecryptionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isDecrypted: Boolean
|
||||||
|
get() = plaintext != null
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Broadcast) return false
|
||||||
|
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getVersion(address: BitmessageAddress): Long {
|
||||||
|
return if (address.version < 4) 4L else 5L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.utils.*
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class CryptoBox : Streamable {
|
||||||
|
|
||||||
|
private val initializationVector: ByteArray
|
||||||
|
private val curveType: Int
|
||||||
|
private val R: ByteArray
|
||||||
|
private val mac: ByteArray
|
||||||
|
private var encrypted: ByteArray
|
||||||
|
|
||||||
|
constructor(data: Streamable, K: ByteArray) : this(Encode.bytes(data), K)
|
||||||
|
|
||||||
|
constructor(data: ByteArray, K: ByteArray) {
|
||||||
|
curveType = 0x02CA
|
||||||
|
|
||||||
|
// 1. The destination public key is called K.
|
||||||
|
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
|
||||||
|
initializationVector = cryptography().randomBytes(16)
|
||||||
|
|
||||||
|
// 3. Generate a new random EC key pair with private key called r and public key called R.
|
||||||
|
val r = cryptography().randomBytes(PRIVATE_KEY_SIZE)
|
||||||
|
R = cryptography().createPublicKey(r)
|
||||||
|
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
|
||||||
|
val P = cryptography().multiply(K, r)
|
||||||
|
val X = Points.getX(P)
|
||||||
|
// 5. Use the X component of public key P and calculate the SHA512 hash H.
|
||||||
|
val H = cryptography().sha512(X)
|
||||||
|
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||||
|
val key_e = Arrays.copyOfRange(H, 0, 32)
|
||||||
|
val key_m = Arrays.copyOfRange(H, 32, 64)
|
||||||
|
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
|
||||||
|
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
|
||||||
|
encrypted = cryptography().crypt(true, data, key_e, initializationVector)
|
||||||
|
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
|
||||||
|
mac = calculateMac(key_m)
|
||||||
|
|
||||||
|
// The resulting data is: IV + R + cipher text + MAC
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(builder: Builder) {
|
||||||
|
initializationVector = builder.initializationVector!!
|
||||||
|
curveType = builder.curveType
|
||||||
|
R = cryptography().createPoint(builder.xComponent!!, builder.yComponent!!)
|
||||||
|
encrypted = builder.encrypted!!
|
||||||
|
mac = builder.mac!!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param k a private key, typically should be 32 bytes long
|
||||||
|
* *
|
||||||
|
* @return an InputStream yielding the decrypted data
|
||||||
|
* *
|
||||||
|
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
|
||||||
|
* *
|
||||||
|
* @see [https://bitmessage.org/wiki/Encryption.Decryption](https://bitmessage.org/wiki/Encryption.Decryption)
|
||||||
|
*/
|
||||||
|
@Throws(DecryptionFailedException::class)
|
||||||
|
fun decrypt(k: ByteArray): InputStream {
|
||||||
|
// 1. The private key used to decrypt is called k.
|
||||||
|
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
|
||||||
|
val P = cryptography().multiply(R, k)
|
||||||
|
// 3. Use the X component of public key P and calculate the SHA512 hash H.
|
||||||
|
val H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33))
|
||||||
|
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||||
|
val key_e = Arrays.copyOfRange(H, 0, 32)
|
||||||
|
val key_m = Arrays.copyOfRange(H, 32, 64)
|
||||||
|
|
||||||
|
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
|
||||||
|
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
|
||||||
|
if (!Arrays.equals(mac, calculateMac(key_m))) {
|
||||||
|
throw DecryptionFailedException()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
|
||||||
|
// and the cipher text as payload. The output is the padded input text.
|
||||||
|
return ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateMac(key_m: ByteArray): ByteArray {
|
||||||
|
val macData = ByteArrayOutputStream()
|
||||||
|
writeWithoutMAC(macData)
|
||||||
|
return cryptography().mac(key_m, macData.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeWithoutMAC(out: OutputStream) {
|
||||||
|
out.write(initializationVector)
|
||||||
|
Encode.int16(curveType, out)
|
||||||
|
writeCoordinateComponent(out, Points.getX(R))
|
||||||
|
writeCoordinateComponent(out, Points.getY(R))
|
||||||
|
out.write(encrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
|
||||||
|
val offset = Bytes.numberOfLeadingZeros(x)
|
||||||
|
val length = x.size - offset
|
||||||
|
Encode.int16(length, out)
|
||||||
|
out.write(x, offset, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
|
||||||
|
val offset = Bytes.numberOfLeadingZeros(x)
|
||||||
|
val length = x.size - offset
|
||||||
|
Encode.int16(length, buffer)
|
||||||
|
buffer.put(x, offset, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
writeWithoutMAC(out)
|
||||||
|
out.write(mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(initializationVector)
|
||||||
|
Encode.int16(curveType, buffer)
|
||||||
|
writeCoordinateComponent(buffer, Points.getX(R))
|
||||||
|
writeCoordinateComponent(buffer, Points.getY(R))
|
||||||
|
buffer.put(encrypted)
|
||||||
|
buffer.put(mac)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
internal var initializationVector: ByteArray? = null
|
||||||
|
internal var curveType: Int = 0
|
||||||
|
internal var xComponent: ByteArray? = null
|
||||||
|
internal var yComponent: ByteArray? = null
|
||||||
|
internal var encrypted: ByteArray? = null
|
||||||
|
internal var mac: ByteArray? = null
|
||||||
|
|
||||||
|
fun IV(initializationVector: ByteArray): Builder {
|
||||||
|
this.initializationVector = initializationVector
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun curveType(curveType: Int): Builder {
|
||||||
|
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType)
|
||||||
|
this.curveType = curveType
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun X(xComponent: ByteArray): Builder {
|
||||||
|
this.xComponent = xComponent
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Y(yComponent: ByteArray): Builder {
|
||||||
|
this.yComponent = yComponent
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encrypted(encrypted: ByteArray): Builder {
|
||||||
|
this.encrypted = encrypted
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MAC(mac: ByteArray): Builder {
|
||||||
|
this.mac = mac
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): CryptoBox {
|
||||||
|
return CryptoBox(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(CryptoBox::class.java)
|
||||||
|
|
||||||
|
@JvmStatic fun read(stream: InputStream, length: Int): CryptoBox {
|
||||||
|
val counter = AccessCounter()
|
||||||
|
return Builder()
|
||||||
|
.IV(Decode.bytes(stream, 16, counter))
|
||||||
|
.curveType(Decode.uint16(stream, counter))
|
||||||
|
.X(Decode.shortVarBytes(stream, counter))
|
||||||
|
.Y(Decode.shortVarBytes(stream, counter))
|
||||||
|
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
|
||||||
|
.MAC(Decode.bytes(stream, 32))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
|
||||||
|
* have to know what it is.
|
||||||
|
*/
|
||||||
|
class GenericPayload(version: Long, override val stream: Long, val data: ByteArray) : ObjectPayload(version) {
|
||||||
|
|
||||||
|
override val type: ObjectType? = null
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is GenericPayload) return false
|
||||||
|
|
||||||
|
if (stream != other.stream) return false
|
||||||
|
return Arrays.equals(data, other.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = (stream xor stream.ushr(32)).toInt()
|
||||||
|
result = 31 * result + Arrays.hashCode(data)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload {
|
||||||
|
return GenericPayload(version, stream, Decode.bytes(`is`, length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for a public key.
|
||||||
|
*/
|
||||||
|
class GetPubkey : ObjectPayload {
|
||||||
|
|
||||||
|
override val type: ObjectType = ObjectType.GET_PUBKEY
|
||||||
|
override val stream: Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an array of bytes that represent either the ripe, or the tag of an address, depending on the
|
||||||
|
* * address version.
|
||||||
|
*/
|
||||||
|
val ripeTag: ByteArray
|
||||||
|
|
||||||
|
constructor(address: BitmessageAddress) : super(address.version) {
|
||||||
|
this.stream = address.stream
|
||||||
|
this.ripeTag = if (address.version < 4) address.ripe else
|
||||||
|
address.tag ?: throw IllegalStateException("Address of version 4 without tag shouldn't exist!")
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(version: Long, stream: Long, ripeOrTag: ByteArray) : super(version) {
|
||||||
|
this.stream = stream
|
||||||
|
this.ripeTag = ripeOrTag
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(ripeTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(ripeTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
|
||||||
|
return GetPubkey(version, stream, Decode.bytes(`is`, length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt
Normal file
103
core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Encrypted
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||||
|
import ch.dissem.bitmessage.entity.PlaintextHolder
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for person-to-person messages.
|
||||||
|
*/
|
||||||
|
class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
||||||
|
|
||||||
|
override val stream: Long
|
||||||
|
private var encrypted: CryptoBox?
|
||||||
|
override var plaintext: Plaintext?
|
||||||
|
private set
|
||||||
|
|
||||||
|
private constructor(stream: Long, encrypted: CryptoBox) : super(1) {
|
||||||
|
this.stream = stream
|
||||||
|
this.encrypted = encrypted
|
||||||
|
this.plaintext = null
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(plaintext: Plaintext) : super(1) {
|
||||||
|
this.stream = plaintext.stream
|
||||||
|
this.encrypted = null
|
||||||
|
this.plaintext = plaintext
|
||||||
|
}
|
||||||
|
|
||||||
|
override val type: ObjectType = ObjectType.MSG
|
||||||
|
|
||||||
|
override val isSigned: Boolean = true
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
||||||
|
}
|
||||||
|
|
||||||
|
override var signature: ByteArray?
|
||||||
|
get() = plaintext?.signature
|
||||||
|
set(signature) {
|
||||||
|
plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encrypt(publicKey: ByteArray) {
|
||||||
|
this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(DecryptionFailedException::class)
|
||||||
|
override fun decrypt(privateKey: ByteArray) {
|
||||||
|
plaintext = Plaintext.read(MSG, encrypted!!.decrypt(privateKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isDecrypted: Boolean
|
||||||
|
get() = plaintext != null
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Msg) return false
|
||||||
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
|
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return stream.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val ACK_LENGTH = 32
|
||||||
|
|
||||||
|
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg {
|
||||||
|
return Msg(stream, CryptoBox.read(`in`, length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The payload of an 'object' command. This is shared by the network.
|
||||||
|
*/
|
||||||
|
abstract class ObjectPayload protected constructor(val version: Long) : Streamable {
|
||||||
|
|
||||||
|
abstract val type: ObjectType?
|
||||||
|
|
||||||
|
abstract val stream: Long
|
||||||
|
|
||||||
|
open val isSigned: Boolean = false
|
||||||
|
|
||||||
|
open fun writeBytesToSign(out: OutputStream) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
||||||
|
* * appended with the data described in this table down to the extra_bytes. Therefore, this must
|
||||||
|
* * be checked and set in the [ObjectMessage] object.
|
||||||
|
*/
|
||||||
|
open var signature: ByteArray? = null
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,31 +14,20 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.entity.payload;
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
|
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
|
||||||
*/
|
*/
|
||||||
public enum ObjectType {
|
enum class ObjectType constructor(val number: Long) {
|
||||||
GET_PUBKEY(0),
|
GET_PUBKEY(0),
|
||||||
PUBKEY(1),
|
PUBKEY(1),
|
||||||
MSG(2),
|
MSG(2),
|
||||||
BROADCAST(3);
|
BROADCAST(3);
|
||||||
|
|
||||||
int number;
|
companion object {
|
||||||
|
@JvmStatic fun fromNumber(number: Long): ObjectType? {
|
||||||
ObjectType(int number) {
|
return values().firstOrNull { it.number == number }
|
||||||
this.number = number;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectType fromNumber(long number) {
|
|
||||||
for (ObjectType type : values()) {
|
|
||||||
if (type.number == number) return type;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNumber() {
|
|
||||||
return number;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||||
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
||||||
|
*/
|
||||||
|
abstract class Pubkey protected constructor(version: Long) : ObjectPayload(version) {
|
||||||
|
|
||||||
|
override val type: ObjectType = ObjectType.PUBKEY
|
||||||
|
|
||||||
|
abstract val signingKey: ByteArray
|
||||||
|
|
||||||
|
abstract val encryptionKey: ByteArray
|
||||||
|
|
||||||
|
abstract val behaviorBitfield: Int
|
||||||
|
|
||||||
|
val ripe: ByteArray by lazy { cryptography().ripemd160(cryptography().sha512(signingKey, encryptionKey)) }
|
||||||
|
|
||||||
|
open val nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
|
||||||
|
open val extraBytes: Long = NETWORK_EXTRA_BYTES
|
||||||
|
|
||||||
|
open fun writeUnencrypted(out: OutputStream) {
|
||||||
|
write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun writeUnencrypted(buffer: ByteBuffer) {
|
||||||
|
write(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bits 0 through 29 are yet undefined
|
||||||
|
*/
|
||||||
|
enum class Feature constructor(bitNumber: Int) {
|
||||||
|
/**
|
||||||
|
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
|
||||||
|
* messages bound for them.
|
||||||
|
*/
|
||||||
|
INCLUDE_DESTINATION(30),
|
||||||
|
/**
|
||||||
|
* If true, the receiving node does send acknowledgements (rather than dropping them).
|
||||||
|
*/
|
||||||
|
DOES_ACK(31);
|
||||||
|
|
||||||
|
// The Bitmessage Protocol Specification starts counting at the most significant bit,
|
||||||
|
// thus the slightly awkward calculation.
|
||||||
|
// https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features
|
||||||
|
private val bit: Int = 1 shl 31 - bitNumber
|
||||||
|
|
||||||
|
fun isActive(bitfield: Int): Boolean {
|
||||||
|
return bitfield and bit != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun bitfield(vararg features: Feature): Int {
|
||||||
|
var bits = 0
|
||||||
|
for (feature in features) {
|
||||||
|
bits = bits or feature.bit
|
||||||
|
}
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun features(bitfield: Int): Array<Feature> {
|
||||||
|
val features = ArrayList<Feature>(Feature.values().size)
|
||||||
|
for (feature in Feature.values()) {
|
||||||
|
if (bitfield and feature.bit != 0) {
|
||||||
|
features.add(feature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return features.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val LATEST_VERSION: Long = 4
|
||||||
|
|
||||||
|
fun getRipe(publicSigningKey: ByteArray, publicEncryptionKey: ByteArray): ByteArray {
|
||||||
|
return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add0x04(key: ByteArray): ByteArray {
|
||||||
|
if (key.size == 65) return key
|
||||||
|
val result = ByteArray(65)
|
||||||
|
result[0] = 4
|
||||||
|
System.arraycopy(key, 0, result, 1, 64)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version 2 public key.
|
||||||
|
*/
|
||||||
|
open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) {
|
||||||
|
|
||||||
|
override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey
|
||||||
|
override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.int32(behaviorBitfield, out)
|
||||||
|
out.write(signingKey, 1, 64)
|
||||||
|
out.write(encryptionKey, 1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.int32(behaviorBitfield, buffer)
|
||||||
|
buffer.put(signingKey, 1, 64)
|
||||||
|
buffer.put(encryptionKey, 1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
internal var streamNumber: Long = 0
|
||||||
|
internal var behaviorBitfield: Int = 0
|
||||||
|
internal var publicSigningKey: ByteArray? = null
|
||||||
|
internal var publicEncryptionKey: ByteArray? = null
|
||||||
|
|
||||||
|
fun stream(streamNumber: Long): Builder {
|
||||||
|
this.streamNumber = streamNumber
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun behaviorBitfield(behaviorBitfield: Int): Builder {
|
||||||
|
this.behaviorBitfield = behaviorBitfield
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
|
||||||
|
this.publicSigningKey = publicSigningKey
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
|
||||||
|
this.publicEncryptionKey = publicEncryptionKey
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): V2Pubkey {
|
||||||
|
return V2Pubkey(
|
||||||
|
version = 2,
|
||||||
|
stream = streamNumber,
|
||||||
|
behaviorBitfield = behaviorBitfield,
|
||||||
|
signingKey = add0x04(publicSigningKey!!),
|
||||||
|
encryptionKey = add0x04(publicEncryptionKey!!)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey {
|
||||||
|
return V2Pubkey(
|
||||||
|
version = 2,
|
||||||
|
stream = stream,
|
||||||
|
behaviorBitfield = Decode.uint32(`in`).toInt(),
|
||||||
|
signingKey = Decode.bytes(`in`, 64),
|
||||||
|
encryptionKey = Decode.bytes(`in`, 64)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version 3 public key.
|
||||||
|
*/
|
||||||
|
class V3Pubkey protected constructor(
|
||||||
|
version: Long, stream: Long, behaviorBitfield: Int,
|
||||||
|
signingKey: ByteArray, encryptionKey: ByteArray,
|
||||||
|
override val nonceTrialsPerByte: Long,
|
||||||
|
override val extraBytes: Long,
|
||||||
|
override var signature: ByteArray? = null
|
||||||
|
) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) {
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
writeBytesToSign(out)
|
||||||
|
Encode.varBytes(
|
||||||
|
signature ?: throw IllegalStateException("signature not available"),
|
||||||
|
out
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
super.write(buffer)
|
||||||
|
Encode.varInt(nonceTrialsPerByte, buffer)
|
||||||
|
Encode.varInt(extraBytes, buffer)
|
||||||
|
Encode.varBytes(
|
||||||
|
signature ?: throw IllegalStateException("signature not available"),
|
||||||
|
buffer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isSigned: Boolean = true
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
super.write(out)
|
||||||
|
Encode.varInt(nonceTrialsPerByte, out)
|
||||||
|
Encode.varInt(extraBytes, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is V3Pubkey) return false
|
||||||
|
return nonceTrialsPerByte == other.nonceTrialsPerByte &&
|
||||||
|
extraBytes == other.extraBytes &&
|
||||||
|
stream == other.stream &&
|
||||||
|
behaviorBitfield == other.behaviorBitfield &&
|
||||||
|
Arrays.equals(signingKey, other.signingKey) &&
|
||||||
|
Arrays.equals(encryptionKey, other.encryptionKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(nonceTrialsPerByte, extraBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
private var streamNumber: Long = 0
|
||||||
|
private var behaviorBitfield: Int = 0
|
||||||
|
private var publicSigningKey: ByteArray? = null
|
||||||
|
private var publicEncryptionKey: ByteArray? = null
|
||||||
|
private var nonceTrialsPerByte: Long = 0
|
||||||
|
private var extraBytes: Long = 0
|
||||||
|
private var signature = ByteArray(0)
|
||||||
|
|
||||||
|
fun stream(streamNumber: Long): Builder {
|
||||||
|
this.streamNumber = streamNumber
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun behaviorBitfield(behaviorBitfield: Int): Builder {
|
||||||
|
this.behaviorBitfield = behaviorBitfield
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
|
||||||
|
this.publicSigningKey = publicSigningKey
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
|
||||||
|
this.publicEncryptionKey = publicEncryptionKey
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
|
||||||
|
this.nonceTrialsPerByte = nonceTrialsPerByte
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extraBytes(extraBytes: Long): Builder {
|
||||||
|
this.extraBytes = extraBytes
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signature(signature: ByteArray): Builder {
|
||||||
|
this.signature = signature
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): V3Pubkey {
|
||||||
|
return V3Pubkey(
|
||||||
|
version = 3,
|
||||||
|
stream = streamNumber,
|
||||||
|
behaviorBitfield = behaviorBitfield,
|
||||||
|
signingKey = publicSigningKey!!,
|
||||||
|
encryptionKey = publicEncryptionKey!!,
|
||||||
|
nonceTrialsPerByte = nonceTrialsPerByte,
|
||||||
|
extraBytes = extraBytes,
|
||||||
|
signature = signature
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey {
|
||||||
|
return V3Pubkey(
|
||||||
|
version = 3,
|
||||||
|
stream = stream,
|
||||||
|
behaviorBitfield = Decode.int32(`is`),
|
||||||
|
signingKey = Decode.bytes(`is`, 64),
|
||||||
|
encryptionKey = Decode.bytes(`is`, 64),
|
||||||
|
nonceTrialsPerByte = Decode.varInt(`is`),
|
||||||
|
extraBytes = Decode.varInt(`is`),
|
||||||
|
signature = Decode.varBytes(`is`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||||
|
* Broadcasts are version 4 or 5.
|
||||||
|
*/
|
||||||
|
open class V4Broadcast : Broadcast {
|
||||||
|
|
||||||
|
override val type: ObjectType = ObjectType.BROADCAST
|
||||||
|
|
||||||
|
protected constructor(version: Long, stream: Long, encrypted: CryptoBox?, plaintext: Plaintext?) : super(version, stream, encrypted, plaintext)
|
||||||
|
|
||||||
|
constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(4, senderAddress.stream, null, plaintext) {
|
||||||
|
if (senderAddress.version >= 4)
|
||||||
|
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast {
|
||||||
|
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Encrypted
|
||||||
|
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||||
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
|
||||||
|
* done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and
|
||||||
|
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
|
||||||
|
* to create messages to be used in spam or in flooding attacks.
|
||||||
|
*/
|
||||||
|
class V4Pubkey : Pubkey, Encrypted {
|
||||||
|
|
||||||
|
override val stream: Long
|
||||||
|
val tag: ByteArray
|
||||||
|
private var encrypted: CryptoBox? = null
|
||||||
|
private var decrypted: V3Pubkey? = null
|
||||||
|
|
||||||
|
private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(4) {
|
||||||
|
this.stream = stream
|
||||||
|
this.tag = tag
|
||||||
|
this.encrypted = encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(decrypted: V3Pubkey) : super(4) {
|
||||||
|
this.stream = decrypted.stream
|
||||||
|
this.decrypted = decrypted
|
||||||
|
this.tag = BitmessageAddress.calculateTag(4, decrypted.stream, decrypted.ripe)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encrypt(publicKey: ByteArray) {
|
||||||
|
if (signature == null) throw IllegalStateException("Pubkey must be signed before encryption.")
|
||||||
|
this.encrypted = CryptoBox(decrypted ?: throw IllegalStateException("no plaintext pubkey data available"), publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(DecryptionFailedException::class)
|
||||||
|
override fun decrypt(privateKey: ByteArray) {
|
||||||
|
decrypted = V3Pubkey.read(encrypted?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"), stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isDecrypted: Boolean
|
||||||
|
get() = decrypted != null
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(tag)
|
||||||
|
encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(tag)
|
||||||
|
encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeUnencrypted(out: OutputStream) {
|
||||||
|
decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeUnencrypted(buffer: ByteBuffer) {
|
||||||
|
decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
out.write(tag)
|
||||||
|
decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val signingKey: ByteArray
|
||||||
|
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
|
||||||
|
override val encryptionKey: ByteArray
|
||||||
|
get() = decrypted?.encryptionKey ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
|
||||||
|
override val behaviorBitfield: Int
|
||||||
|
get() = decrypted?.behaviorBitfield ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
|
||||||
|
override var signature: ByteArray?
|
||||||
|
get() = decrypted?.signature
|
||||||
|
set(signature) {
|
||||||
|
decrypted?.signature = signature
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isSigned: Boolean = true
|
||||||
|
|
||||||
|
override val nonceTrialsPerByte: Long
|
||||||
|
get() = decrypted?.nonceTrialsPerByte ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
|
||||||
|
override val extraBytes: Long
|
||||||
|
get() = decrypted?.extraBytes ?: throw IllegalStateException("pubkey is encrypted")
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is V4Pubkey) return false
|
||||||
|
|
||||||
|
if (stream != other.stream) return false
|
||||||
|
if (!Arrays.equals(tag, other.tag)) return false
|
||||||
|
return !if (decrypted != null) decrypted != other.decrypted else other.decrypted != null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = (stream xor stream.ushr(32)).toInt()
|
||||||
|
result = 31 * result + Arrays.hashCode(tag)
|
||||||
|
result = 31 * result + if (decrypted != null) decrypted!!.hashCode() else 0
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey {
|
||||||
|
if (encrypted)
|
||||||
|
return V4Pubkey(stream,
|
||||||
|
Decode.bytes(`in`, 32),
|
||||||
|
CryptoBox.read(`in`, length - 32))
|
||||||
|
else
|
||||||
|
return V4Pubkey(V3Pubkey.read(`in`, stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.payload
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||||
|
*/
|
||||||
|
class V5Broadcast : V4Broadcast {
|
||||||
|
|
||||||
|
val tag: ByteArray
|
||||||
|
|
||||||
|
private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(5, stream, encrypted, null) {
|
||||||
|
this.tag = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(5, senderAddress.stream, null, plaintext) {
|
||||||
|
if (senderAddress.version < 4)
|
||||||
|
throw IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.version)
|
||||||
|
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeBytesToSign(out: OutputStream) {
|
||||||
|
out.write(tag)
|
||||||
|
super.writeBytesToSign(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(tag)
|
||||||
|
super.write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast {
|
||||||
|
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject
|
||||||
|
|
||||||
|
import ch.dissem.msgpack.types.MPMap
|
||||||
|
import ch.dissem.msgpack.types.MPString
|
||||||
|
import ch.dissem.msgpack.types.MPType
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.zip.DeflaterOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended encoding message object.
|
||||||
|
*/
|
||||||
|
data class ExtendedEncoding(val content: ExtendedEncoding.ExtendedType) : Serializable {
|
||||||
|
|
||||||
|
val type: String? = content.type
|
||||||
|
|
||||||
|
fun zip(): ByteArray {
|
||||||
|
ByteArrayOutputStream().use { out ->
|
||||||
|
DeflaterOutputStream(out).use { zipper -> content.pack().pack(zipper) }
|
||||||
|
return out.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Unpacker<out T : ExtendedType> {
|
||||||
|
val type: String
|
||||||
|
|
||||||
|
fun unpack(map: MPMap<MPString, MPType<*>>): T
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtendedType : Serializable {
|
||||||
|
val type: String
|
||||||
|
|
||||||
|
fun pack(): MPMap<MPString, MPType<*>>
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.utils.Strings
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class InventoryVector constructor(
|
||||||
|
/**
|
||||||
|
* Hash of the object
|
||||||
|
*/
|
||||||
|
val hash: ByteArray) : Streamable {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is InventoryVector) return false
|
||||||
|
|
||||||
|
return Arrays.equals(hash, other.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Arrays.hashCode(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
out.write(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
buffer.put(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return Strings.hex(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? {
|
||||||
|
return InventoryVector(
|
||||||
|
hash ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class Label(
|
||||||
|
private val label: String,
|
||||||
|
val type: Label.Type? = null,
|
||||||
|
/**
|
||||||
|
* RGBA representation for the color.
|
||||||
|
*/
|
||||||
|
var color: Int = 0,
|
||||||
|
var ord: Int = 1000
|
||||||
|
) : Serializable {
|
||||||
|
|
||||||
|
var id: Any? = null
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Label) return false
|
||||||
|
return label == other.label
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
INBOX,
|
||||||
|
BROADCAST,
|
||||||
|
DRAFT,
|
||||||
|
OUTBOX,
|
||||||
|
SENT,
|
||||||
|
UNREAD,
|
||||||
|
TRASH
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.Version
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Socket
|
||||||
|
import java.net.SocketAddress
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
fun ip6(inetAddress: InetAddress): ByteArray {
|
||||||
|
val address = inetAddress.address
|
||||||
|
when (address.size) {
|
||||||
|
16 -> {
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
val ip6 = ByteArray(16)
|
||||||
|
ip6[10] = 0xff.toByte()
|
||||||
|
ip6[11] = 0xff.toByte()
|
||||||
|
System.arraycopy(address, 0, ip6, 12, 4)
|
||||||
|
return ip6
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("Weird address " + inetAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node's address. It's written in IPv6 format.
|
||||||
|
*/
|
||||||
|
data class NetworkAddress(
|
||||||
|
var time: Long,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream number for this node
|
||||||
|
*/
|
||||||
|
val stream: Long,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* same service(s) listed in version
|
||||||
|
*/
|
||||||
|
val services: Long,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address
|
||||||
|
* (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address).
|
||||||
|
*/
|
||||||
|
val IPv6: ByteArray,
|
||||||
|
val port: Int
|
||||||
|
) : Streamable {
|
||||||
|
|
||||||
|
constructor(time: Long, stream: Long, services: Long = 1, socket: Socket)
|
||||||
|
: this(time, stream, services, ip6(socket.inetAddress), socket.port)
|
||||||
|
|
||||||
|
constructor(time: Long, stream: Long, services: Long = 1, inetAddress: InetAddress, port: Int)
|
||||||
|
: this(time, stream, services, ip6(inetAddress), port)
|
||||||
|
|
||||||
|
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
|
||||||
|
|
||||||
|
fun toInetAddress(): InetAddress {
|
||||||
|
return InetAddress.getByAddress(IPv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is NetworkAddress) return false
|
||||||
|
|
||||||
|
return port == other.port && Arrays.equals(IPv6, other.IPv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = Arrays.hashCode(IPv6)
|
||||||
|
result = 31 * result + port
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "[" + toInetAddress() + "]:" + port
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
write(out, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(out: OutputStream, light: Boolean) {
|
||||||
|
if (!light) {
|
||||||
|
Encode.int64(time, out)
|
||||||
|
Encode.int32(stream, out)
|
||||||
|
}
|
||||||
|
Encode.int64(services, out)
|
||||||
|
out.write(IPv6)
|
||||||
|
Encode.int16(port, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
write(buffer, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(buffer: ByteBuffer, light: Boolean) {
|
||||||
|
if (!light) {
|
||||||
|
Encode.int64(time, buffer)
|
||||||
|
Encode.int32(stream, buffer)
|
||||||
|
}
|
||||||
|
Encode.int64(services, buffer)
|
||||||
|
buffer.put(IPv6)
|
||||||
|
Encode.int16(port, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
internal var time: Long? = null
|
||||||
|
internal var stream: Long = 0
|
||||||
|
internal var services: Long = 1
|
||||||
|
internal var ipv6: ByteArray? = null
|
||||||
|
internal var port: Int = 0
|
||||||
|
|
||||||
|
fun time(time: Long): Builder {
|
||||||
|
this.time = time
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stream(stream: Long): Builder {
|
||||||
|
this.stream = stream
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun services(services: Long): Builder {
|
||||||
|
this.services = services
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ip(inetAddress: InetAddress): Builder {
|
||||||
|
ipv6 = ip6(inetAddress)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ipv6(ipv6: ByteArray): Builder {
|
||||||
|
this.ipv6 = ipv6
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ipv6(p00: Int, p01: Int, p02: Int, p03: Int,
|
||||||
|
p04: Int, p05: Int, p06: Int, p07: Int,
|
||||||
|
p08: Int, p09: Int, p10: Int, p11: Int,
|
||||||
|
p12: Int, p13: Int, p14: Int, p15: Int): Builder {
|
||||||
|
this.ipv6 = byteArrayOf(p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte(), p04.toByte(), p05.toByte(), p06.toByte(), p07.toByte(), p08.toByte(), p09.toByte(), p10.toByte(), p11.toByte(), p12.toByte(), p13.toByte(), p14.toByte(), p15.toByte())
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ipv4(p00: Int, p01: Int, p02: Int, p03: Int): Builder {
|
||||||
|
this.ipv6 = byteArrayOf(0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0xff.toByte(), 0xff.toByte(), p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte())
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun port(port: Int): Builder {
|
||||||
|
this.port = port
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun address(address: SocketAddress): Builder {
|
||||||
|
if (address is InetSocketAddress) {
|
||||||
|
ip(address.address)
|
||||||
|
port(address.port)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Unknown type of address: " + address.javaClass)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): NetworkAddress {
|
||||||
|
return NetworkAddress(
|
||||||
|
time ?: UnixTime.now, stream, services, ipv6!!, port
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Streamable
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.utils.Bytes
|
||||||
|
import ch.dissem.bitmessage.utils.Decode
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import java.io.*
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
|
||||||
|
* [Pubkey] object.
|
||||||
|
*/
|
||||||
|
data class PrivateKey(
|
||||||
|
val privateSigningKey: ByteArray,
|
||||||
|
val privateEncryptionKey: ByteArray,
|
||||||
|
|
||||||
|
val pubkey: Pubkey
|
||||||
|
) : Streamable {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
shorter: Boolean,
|
||||||
|
stream: Long,
|
||||||
|
nonceTrialsPerByte: Long, extraBytes: Long,
|
||||||
|
vararg features: Pubkey.Feature
|
||||||
|
) : this(
|
||||||
|
Builder(version = Pubkey.LATEST_VERSION, stream = stream, shorter = shorter)
|
||||||
|
.random()
|
||||||
|
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||||
|
.extraBytes(extraBytes)
|
||||||
|
.features(features)
|
||||||
|
.generate())
|
||||||
|
|
||||||
|
constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase)
|
||||||
|
|
||||||
|
constructor(version: Long, stream: Long, passphrase: String) : this(
|
||||||
|
Builder(version, stream, false).seed(passphrase).generate()
|
||||||
|
)
|
||||||
|
|
||||||
|
private constructor(builder: Builder) : this(
|
||||||
|
builder.privSK!!, builder.privEK!!,
|
||||||
|
Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!,
|
||||||
|
builder.nonceTrialsPerByte, builder.extraBytes, *builder.features)
|
||||||
|
)
|
||||||
|
|
||||||
|
private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) {
|
||||||
|
|
||||||
|
internal var seed: ByteArray? = null
|
||||||
|
internal var nextNonce: Long = 0
|
||||||
|
|
||||||
|
internal var privSK: ByteArray? = null
|
||||||
|
internal var privEK: ByteArray? = null
|
||||||
|
internal var pubSK: ByteArray? = null
|
||||||
|
internal var pubEK: ByteArray? = null
|
||||||
|
|
||||||
|
internal var nonceTrialsPerByte = InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||||
|
internal var extraBytes = InternalContext.NETWORK_EXTRA_BYTES
|
||||||
|
internal var features: Array<out Pubkey.Feature> = emptyArray()
|
||||||
|
|
||||||
|
internal fun random(): Builder {
|
||||||
|
seed = cryptography().randomBytes(1024)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
|
||||||
|
this.nonceTrialsPerByte = nonceTrialsPerByte
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extraBytes(extraBytes: Long): Builder {
|
||||||
|
this.extraBytes = extraBytes
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun features(features: Array<out Pubkey.Feature>): Builder {
|
||||||
|
this.features = features
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun seed(passphrase: String): Builder {
|
||||||
|
try {
|
||||||
|
seed = passphrase.toByteArray(charset("UTF-8"))
|
||||||
|
} catch (e: UnsupportedEncodingException) {
|
||||||
|
throw ApplicationException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun generate(): Builder {
|
||||||
|
var signingKeyNonce = nextNonce
|
||||||
|
var encryptionKeyNonce = nextNonce + 1
|
||||||
|
var ripe: ByteArray
|
||||||
|
do {
|
||||||
|
privEK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(encryptionKeyNonce)), 32)
|
||||||
|
privSK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(signingKeyNonce)), 32)
|
||||||
|
pubSK = cryptography().createPublicKey(privSK!!)
|
||||||
|
pubEK = cryptography().createPublicKey(privEK!!)
|
||||||
|
ripe = cryptography().ripemd160(cryptography().sha512(pubSK!!, pubEK!!))
|
||||||
|
|
||||||
|
signingKeyNonce += 2
|
||||||
|
encryptionKeyNonce += 2
|
||||||
|
} while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0)
|
||||||
|
nextNonce = signingKeyNonce
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(out: OutputStream) {
|
||||||
|
Encode.varInt(pubkey.version, out)
|
||||||
|
Encode.varInt(pubkey.stream, out)
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
pubkey.writeUnencrypted(baos)
|
||||||
|
Encode.varInt(baos.size(), out)
|
||||||
|
out.write(baos.toByteArray())
|
||||||
|
Encode.varBytes(privateSigningKey, out)
|
||||||
|
Encode.varBytes(privateEncryptionKey, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun write(buffer: ByteBuffer) {
|
||||||
|
Encode.varInt(pubkey.version, buffer)
|
||||||
|
Encode.varInt(pubkey.stream, buffer)
|
||||||
|
try {
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
pubkey.writeUnencrypted(baos)
|
||||||
|
Encode.varBytes(baos.toByteArray(), buffer)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw ApplicationException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
Encode.varBytes(privateSigningKey, buffer)
|
||||||
|
Encode.varBytes(privateEncryptionKey, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?) = other is PrivateKey
|
||||||
|
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
|
||||||
|
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
|
||||||
|
&& pubkey == other.pubkey
|
||||||
|
|
||||||
|
override fun hashCode() = pubkey.hashCode()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val PRIVATE_KEY_SIZE = 32
|
||||||
|
|
||||||
|
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
|
||||||
|
val result = ArrayList<PrivateKey>(numberOfAddresses)
|
||||||
|
val builder = Builder(version, stream, shorter).seed(passphrase)
|
||||||
|
for (i in 0..numberOfAddresses - 1) {
|
||||||
|
builder.generate()
|
||||||
|
result.add(PrivateKey(builder))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic fun read(`is`: InputStream): PrivateKey {
|
||||||
|
val version = Decode.varInt(`is`).toInt()
|
||||||
|
val stream = Decode.varInt(`is`)
|
||||||
|
val len = Decode.varInt(`is`).toInt()
|
||||||
|
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
|
||||||
|
val signingKey = Decode.varBytes(`is`)
|
||||||
|
val encryptionKey = Decode.varBytes(`is`)
|
||||||
|
return PrivateKey(signingKey, encryptionKey, pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject.extended
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "file" attachment as used by extended encoding type messages. Could either be an attachment,
|
||||||
|
* or used inline to be used by a HTML message, for example.
|
||||||
|
*/
|
||||||
|
data class Attachment constructor(
|
||||||
|
val name: String,
|
||||||
|
val data: ByteArray,
|
||||||
|
val type: String,
|
||||||
|
val disposition: Disposition
|
||||||
|
) : Serializable {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Attachment) return false
|
||||||
|
return name == other.name &&
|
||||||
|
Arrays.equals(data, other.data) &&
|
||||||
|
type == other.type &&
|
||||||
|
disposition == other.disposition
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(name, data, type, disposition)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Disposition {
|
||||||
|
inline, attachment
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
private var name: String? = null
|
||||||
|
private var data: ByteArray? = null
|
||||||
|
private var type: String? = null
|
||||||
|
private var disposition: Disposition? = null
|
||||||
|
|
||||||
|
fun name(name: String): Builder {
|
||||||
|
this.name = name
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun data(data: ByteArray): Builder {
|
||||||
|
this.data = data
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun type(type: String): Builder {
|
||||||
|
this.type = type
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun inline(): Builder {
|
||||||
|
this.disposition = Disposition.inline
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attachment(): Builder {
|
||||||
|
this.disposition = Disposition.attachment
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disposition(disposition: Disposition): Builder {
|
||||||
|
this.disposition = disposition
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): Attachment {
|
||||||
|
return Attachment(name!!, data!!, type!!, disposition!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject.extended
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.utils.Strings.str
|
||||||
|
import ch.dissem.msgpack.types.*
|
||||||
|
import ch.dissem.msgpack.types.Utils.mp
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.URLConnection
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work
|
||||||
|
* properly with future PyBitmessage implementations.
|
||||||
|
*/
|
||||||
|
data class Message constructor(
|
||||||
|
val subject: String,
|
||||||
|
val body: String,
|
||||||
|
val parents: List<InventoryVector>,
|
||||||
|
val files: List<Attachment>
|
||||||
|
) : ExtendedEncoding.ExtendedType {
|
||||||
|
|
||||||
|
override val type: String = TYPE
|
||||||
|
|
||||||
|
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||||
|
val result = MPMap<MPString, MPType<*>>()
|
||||||
|
result.put(mp(""), mp(TYPE))
|
||||||
|
result.put(mp("subject"), mp(subject))
|
||||||
|
result.put(mp("body"), mp(body))
|
||||||
|
|
||||||
|
if (!files.isEmpty()) {
|
||||||
|
val items = MPArray<MPMap<MPString, MPType<*>>>()
|
||||||
|
result.put(mp("files"), items)
|
||||||
|
for (file in files) {
|
||||||
|
val item = MPMap<MPString, MPType<*>>()
|
||||||
|
item.put(mp("name"), mp(file.name))
|
||||||
|
item.put(mp("data"), mp(*file.data))
|
||||||
|
item.put(mp("type"), mp(file.type))
|
||||||
|
item.put(mp("disposition"), mp(file.disposition.name))
|
||||||
|
items.add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!parents.isEmpty()) {
|
||||||
|
val items = MPArray<MPBinary>()
|
||||||
|
result.put(mp("parents"), items)
|
||||||
|
for ((hash) in parents) {
|
||||||
|
items.add(mp(*hash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
private var subject: String? = null
|
||||||
|
private var body: String? = null
|
||||||
|
private val parents = LinkedList<InventoryVector>()
|
||||||
|
private val files = LinkedList<Attachment>()
|
||||||
|
|
||||||
|
fun subject(subject: String): Builder {
|
||||||
|
this.subject = subject
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun body(body: String): Builder {
|
||||||
|
this.body = body
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addParent(parent: Plaintext?): Builder {
|
||||||
|
if (parent != null) {
|
||||||
|
val iv = parent.inventoryVector
|
||||||
|
if (iv == null) {
|
||||||
|
LOG.debug("Ignored parent without IV")
|
||||||
|
} else {
|
||||||
|
parents.add(iv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addParent(iv: InventoryVector?): Builder {
|
||||||
|
if (iv != null) {
|
||||||
|
parents.add(iv)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFile(file: File?, disposition: Attachment.Disposition): Builder {
|
||||||
|
if (file != null) {
|
||||||
|
try {
|
||||||
|
files.add(Attachment.Builder()
|
||||||
|
.name(file.name)
|
||||||
|
.disposition(disposition)
|
||||||
|
.type(URLConnection.guessContentTypeFromStream(FileInputStream(file)))
|
||||||
|
.data(Files.readAllBytes(file.toPath()))
|
||||||
|
.build())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
LOG.error(e.message, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFile(file: Attachment?): Builder {
|
||||||
|
if (file != null) {
|
||||||
|
files.add(file)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): ExtendedEncoding {
|
||||||
|
return ExtendedEncoding(Message(subject!!, body!!, parents, files))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Unpacker : ExtendedEncoding.Unpacker<Message> {
|
||||||
|
override val type: String = TYPE
|
||||||
|
|
||||||
|
override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
|
||||||
|
val subject = str(map[mp("subject")]) ?: ""
|
||||||
|
val body = str(map[mp("body")]) ?: ""
|
||||||
|
val parents = LinkedList<InventoryVector>()
|
||||||
|
val files = LinkedList<Attachment>()
|
||||||
|
val mpParents = map[mp("parents")] as? MPArray<*>
|
||||||
|
for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) {
|
||||||
|
parents.add(InventoryVector.fromHash(
|
||||||
|
(parent as? MPBinary)?.value ?: continue
|
||||||
|
) ?: continue)
|
||||||
|
}
|
||||||
|
val mpFiles = map[mp("files")] as? MPArray<*>
|
||||||
|
for (item in mpFiles ?: emptyList<Any>()) {
|
||||||
|
if (item is MPMap<*, *>) {
|
||||||
|
val b = Attachment.Builder()
|
||||||
|
b.name(str(item[mp("name")])!!)
|
||||||
|
b.data(
|
||||||
|
bin(item[mp("data")] ?: continue) ?: continue
|
||||||
|
)
|
||||||
|
b.type(str(item[mp("type")])!!)
|
||||||
|
val disposition = str(item[mp("disposition")])
|
||||||
|
if ("inline" == disposition) {
|
||||||
|
b.inline()
|
||||||
|
} else if ("attachment" == disposition) {
|
||||||
|
b.attachment()
|
||||||
|
}
|
||||||
|
files.add(b.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Message(subject, body, parents, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bin(data: MPType<*>): ByteArray? {
|
||||||
|
return (data as? MPBinary)?.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(Message::class.java)
|
||||||
|
|
||||||
|
val TYPE = "message"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.entity.valueobject.extended
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.utils.Strings.str
|
||||||
|
import ch.dissem.msgpack.types.MPBinary
|
||||||
|
import ch.dissem.msgpack.types.MPMap
|
||||||
|
import ch.dissem.msgpack.types.MPString
|
||||||
|
import ch.dissem.msgpack.types.MPType
|
||||||
|
import ch.dissem.msgpack.types.Utils.mp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended encoding type 'vote'. Specification still outstanding, so this will need some work.
|
||||||
|
*/
|
||||||
|
data class Vote constructor(val msgId: InventoryVector, val vote: String) : ExtendedEncoding.ExtendedType {
|
||||||
|
|
||||||
|
override val type: String = TYPE
|
||||||
|
|
||||||
|
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||||
|
val result = MPMap<MPString, MPType<*>>()
|
||||||
|
result.put(mp(""), mp(TYPE))
|
||||||
|
result.put(mp("msgId"), mp(*msgId.hash))
|
||||||
|
result.put(mp("vote"), mp(vote))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
private var msgId: InventoryVector? = null
|
||||||
|
private var vote: String? = null
|
||||||
|
|
||||||
|
fun up(message: Plaintext): ExtendedEncoding {
|
||||||
|
msgId = message.inventoryVector
|
||||||
|
vote = "1"
|
||||||
|
return ExtendedEncoding(Vote(msgId!!, vote!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun down(message: Plaintext): ExtendedEncoding {
|
||||||
|
msgId = message.inventoryVector
|
||||||
|
vote = "-1"
|
||||||
|
return ExtendedEncoding(Vote(msgId!!, vote!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun msgId(iv: InventoryVector): Builder {
|
||||||
|
this.msgId = iv
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun vote(vote: String): Builder {
|
||||||
|
this.vote = vote
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): ExtendedEncoding {
|
||||||
|
return ExtendedEncoding(Vote(msgId!!, vote!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Unpacker : ExtendedEncoding.Unpacker<Vote> {
|
||||||
|
override val type: String
|
||||||
|
get() = TYPE
|
||||||
|
|
||||||
|
override fun unpack(map: MPMap<MPString, MPType<*>>): Vote {
|
||||||
|
val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
|
||||||
|
val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given")
|
||||||
|
return Vote(msgId, vote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val TYPE = "vote"
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,13 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.exception;
|
package ch.dissem.bitmessage.exception
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates an illegal Bitmessage address
|
* Indicates an illegal Bitmessage address
|
||||||
*/
|
*/
|
||||||
public class AddressFormatException extends RuntimeException {
|
class AddressFormatException(message: String) : RuntimeException(message)
|
||||||
public AddressFormatException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2015 Christian Basler
|
* Copyright 2017 Christian Basler
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,14 +14,15 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.bitmessage.ports;
|
package ch.dissem.bitmessage.exception
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
public interface CustomCommandHandler {
|
class ApplicationException : RuntimeException {
|
||||||
MessagePayload handle(CustomMessage request);
|
|
||||||
|
constructor(cause: Throwable) : super(cause)
|
||||||
|
|
||||||
|
constructor(message: String) : super(message)
|
||||||
|
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user