Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
@ -1,9 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
name: default
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: test
|
|
||||||
image: adoptopenjdk/openjdk8:alpine
|
|
||||||
commands:
|
|
||||||
- ./gradlew assemble
|
|
||||||
- ./gradlew check
|
|
@ -1,7 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
charset = utf-8
|
|
||||||
indent_size = 4
|
|
165
.gitignore
vendored
165
.gitignore
vendored
@ -1,162 +1,3 @@
|
|||||||
# Created by https://www.gitignore.io
|
_site
|
||||||
|
.sass-cache
|
||||||
*.log
|
.jekyll-metadata
|
||||||
|
|
||||||
### Gradle ###
|
|
||||||
.gradle
|
|
||||||
build/
|
|
||||||
classes/
|
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
|
||||||
gradle-app.setting
|
|
||||||
|
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
|
||||||
!gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
### Intellij ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
|
|
||||||
|
|
||||||
*.iml
|
|
||||||
|
|
||||||
## Directory-based project format:
|
|
||||||
.idea/
|
|
||||||
# if you remove the above rule, at least ignore the following:
|
|
||||||
|
|
||||||
# User-specific stuff:
|
|
||||||
# .idea/workspace.xml
|
|
||||||
# .idea/tasks.xml
|
|
||||||
# .idea/dictionaries
|
|
||||||
|
|
||||||
# Sensitive or high-churn files:
|
|
||||||
# .idea/dataSources.ids
|
|
||||||
# .idea/dataSources.xml
|
|
||||||
# .idea/sqlDataSources.xml
|
|
||||||
# .idea/dynamic.xml
|
|
||||||
# .idea/uiDesigner.xml
|
|
||||||
|
|
||||||
# Gradle:
|
|
||||||
# .idea/gradle.xml
|
|
||||||
# .idea/libraries
|
|
||||||
|
|
||||||
# Mongo Explorer plugin:
|
|
||||||
# .idea/mongoSettings.xml
|
|
||||||
|
|
||||||
## File-based project format:
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
|
|
||||||
|
|
||||||
### Eclipse ###
|
|
||||||
*.pydevproject
|
|
||||||
.metadata
|
|
||||||
.gradle
|
|
||||||
bin/
|
|
||||||
tmp/
|
|
||||||
*.tmp
|
|
||||||
*.bak
|
|
||||||
*.swp
|
|
||||||
*~.nib
|
|
||||||
local.properties
|
|
||||||
.settings/
|
|
||||||
.loadpath
|
|
||||||
|
|
||||||
# Eclipse Core
|
|
||||||
.project
|
|
||||||
|
|
||||||
# External tool builders
|
|
||||||
.externalToolBuilders/
|
|
||||||
|
|
||||||
# Locally stored "Eclipse launch configurations"
|
|
||||||
*.launch
|
|
||||||
|
|
||||||
# CDT-specific
|
|
||||||
.cproject
|
|
||||||
|
|
||||||
# JDT-specific (Eclipse Java Development Tools)
|
|
||||||
.classpath
|
|
||||||
|
|
||||||
# PDT-specific
|
|
||||||
.buildpath
|
|
||||||
|
|
||||||
# sbteclipse plugin
|
|
||||||
.target
|
|
||||||
|
|
||||||
# TeXlipse plugin
|
|
||||||
.texlipse
|
|
||||||
|
|
||||||
|
|
||||||
### Linux ###
|
|
||||||
*~
|
|
||||||
|
|
||||||
# KDE directory preferences
|
|
||||||
.directory
|
|
||||||
|
|
||||||
# Linux trash folder which might appear on any partition or disk
|
|
||||||
.Trash-*
|
|
||||||
|
|
||||||
|
|
||||||
### OSX ###
|
|
||||||
.DS_Store
|
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Icon must end with two \r
|
|
||||||
Icon
|
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
|
||||||
._*
|
|
||||||
|
|
||||||
# Files that might appear in the root of a volume
|
|
||||||
.DocumentRevisions-V100
|
|
||||||
.fseventsd
|
|
||||||
.Spotlight-V100
|
|
||||||
.TemporaryItems
|
|
||||||
.Trashes
|
|
||||||
.VolumeIcon.icns
|
|
||||||
|
|
||||||
# Directories potentially created on remote AFP share
|
|
||||||
.AppleDB
|
|
||||||
.AppleDesktop
|
|
||||||
Network Trash Folder
|
|
||||||
Temporary Items
|
|
||||||
.apdisk
|
|
||||||
|
|
||||||
|
|
||||||
### Windows ###
|
|
||||||
# Windows image file caches
|
|
||||||
Thumbs.db
|
|
||||||
ehthumbs.db
|
|
||||||
|
|
||||||
# Folder config file
|
|
||||||
Desktop.ini
|
|
||||||
|
|
||||||
# Recycle Bin used on file shares
|
|
||||||
$RECYCLE.BIN/
|
|
||||||
|
|
||||||
# Windows Installer files
|
|
||||||
*.cab
|
|
||||||
*.msi
|
|
||||||
*.msm
|
|
||||||
*.msp
|
|
||||||
|
|
||||||
# Windows shortcuts
|
|
||||||
*.lnk
|
|
||||||
|
|
||||||
|
10
.travis.yml
10
.travis.yml
@ -1,10 +0,0 @@
|
|||||||
language: java
|
|
||||||
sudo: false # faster builds
|
|
||||||
jdk:
|
|
||||||
- oraclejdk8
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- pip install --user codecov
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- codecov
|
|
1308
Bitmessage.uml
1308
Bitmessage.uml
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
We love pull requests from everyone. Please be nice and forgive us
|
|
||||||
if we can't process your request right away.
|
|
||||||
|
|
||||||
Fork, then clone the repo:
|
|
||||||
|
|
||||||
git clone git@github.com:your-username/Jabit.git
|
|
||||||
|
|
||||||
Make sure the tests pass:
|
|
||||||
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
Make your change. Add tests for your change. Make the tests pass:
|
|
||||||
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
Push to your fork and [submit a pull request][pr].
|
|
||||||
|
|
||||||
[pr]: https://github.com/Dissem/Jabit/compare/
|
|
||||||
|
|
||||||
Unfortunately we can't always answer right away, so we ask you to have
|
|
||||||
some patience. Then we may suggest some changes or improvements or
|
|
||||||
alternatives.
|
|
201
LICENSE
201
LICENSE
@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
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.
|
|
137
README.md
137
README.md
@ -1,137 +0,0 @@
|
|||||||
Jabit
|
|
||||||
=====
|
|
||||||
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/ch.dissem.jabit/jabit-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/ch.dissem.jabit/jabit-core)
|
|
||||||
[![Javadoc](https://javadoc-emblem.rhcloud.com/doc/ch.dissem.jabit/jabit-core/badge.svg)](http://www.javadoc.io/doc/ch.dissem.jabit/jabit-core)
|
|
||||||
[![Apache 2](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE)
|
|
||||||
[![Visit our IRC channel](https://img.shields.io/badge/irc-%23jabit-blue.svg)](https://kiwiirc.com/client/irc.freenode.net/#jabit)
|
|
||||||
|
|
||||||
A Java implementation for the Bitmessage protocol. To build, use command `./gradlew build`.
|
|
||||||
|
|
||||||
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
|
|
||||||
[![Build Status](https://travis-ci.org/Dissem/Jabit.svg?branch=master)](https://travis-ci.org/Dissem/Jabit)
|
|
||||||
[![Code Quality](https://img.shields.io/codacy/e9938d2adbb74a0db553115bef692ff3/master.svg)](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144281)
|
|
||||||
[![Test Coverage](https://codecov.io/github/Dissem/Jabit/coverage.svg?branch=master)](https://codecov.io/github/Dissem/Jabit?branch=master)
|
|
||||||
|
|
||||||
#### Develop
|
|
||||||
[![Build Status](https://travis-ci.org/Dissem/Jabit.svg?branch=develop)](https://travis-ci.org/Dissem/Jabit?branch=develop)
|
|
||||||
[![Code Quality](https://img.shields.io/codacy/e9938d2adbb74a0db553115bef692ff3/develop.svg)](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144279)
|
|
||||||
[![Test Coverage](https://codecov.io/github/Dissem/Jabit/coverage.svg?branch=develop)](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
|
|
||||||
--------
|
|
||||||
|
|
||||||
There are most probably some security issues, me programming this thing all by myself. Jabit doesn't do anything against timing attacks yet, for example. Please feel free to use the library, report bugs and maybe even help out. I hope the code is easy to understand and work with.
|
|
||||||
|
|
||||||
Project Status
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Basically, everything needed for a working Bitmessage client is there:
|
|
||||||
* Creating new identities (private addresses)
|
|
||||||
* Adding contacts and subscriptions
|
|
||||||
* Receiving broadcasts
|
|
||||||
* Receiving messages
|
|
||||||
* Sending messages and broadcasts
|
|
||||||
* Managing outgoing and incoming connections
|
|
||||||
* Initialise and manage a registry of Bitmessage network nodes
|
|
||||||
* An easy to use API
|
|
||||||
* A command line demo application built using the API
|
|
||||||
|
|
||||||
Setup
|
|
||||||
-----
|
|
||||||
|
|
||||||
It is recommended to define the version like this:
|
|
||||||
```Gradle
|
|
||||||
ext.jabitVersion = '2.0.0'
|
|
||||||
```
|
|
||||||
Add Jabit as Gradle dependency:
|
|
||||||
```Gradle
|
|
||||||
compile "ch.dissem.jabit:jabit-core:$jabitVersion"
|
|
||||||
```
|
|
||||||
Unless you want to implement your own, also add the following:
|
|
||||||
```Gradle
|
|
||||||
compile "ch.dissem.jabit:jabit-networking:$jabitVersion"
|
|
||||||
compile "ch.dissem.jabit:jabit-repositories:$jabitVersion"
|
|
||||||
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:
|
|
||||||
```Gradle
|
|
||||||
compile "ch.dissem.jabit:jabit-wif:$jabitVersion"
|
|
||||||
```
|
|
||||||
|
|
||||||
For Android clients use `jabit-cryptography-spongy` instead of `jabit-cryptography-bouncy`.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
First, you'll need to create a `BitmessageContext`:
|
|
||||||
```Java
|
|
||||||
JdbcConfig jdbcConfig = new JdbcConfig();
|
|
||||||
BitmessageContext ctx = new BitmessageContext.Builder()
|
|
||||||
.addressRepo(new JdbcAddressRepository(jdbcConfig))
|
|
||||||
.inventory(new JdbcInventory(jdbcConfig))
|
|
||||||
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
|
||||||
.powRepo(new JdbcProofOfWorkRepository(jdbcConfig))
|
|
||||||
.nodeRegistry(new JdbcNodeRegistry(jdbcConfig))
|
|
||||||
.networkHandler(new NioNetworkHandler())
|
|
||||||
.cryptography(new BouncyCryptography())
|
|
||||||
.listener(System.out::println)
|
|
||||||
.build();
|
|
||||||
```
|
|
||||||
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
|
|
||||||
```Java
|
|
||||||
.listener(new BitmessageContext.Listener() {
|
|
||||||
@Override
|
|
||||||
public void receive(Plaintext plaintext) {
|
|
||||||
// TODO: Notify the user
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Next you'll need to start the context:
|
|
||||||
```Java
|
|
||||||
ctx.startup()
|
|
||||||
```
|
|
||||||
Then you might want to create an identity
|
|
||||||
```Java
|
|
||||||
BitmessageAddress identity = ctx.createIdentity(false, Pubkey.Feature.DOES_ACK);
|
|
||||||
```
|
|
||||||
or add some contacts
|
|
||||||
```Java
|
|
||||||
BitmessageAddress contact = new BitmessageAddress("BM-2cTarrmjMdRicKZ4qQ8A13JhoR3Uq6Zh5j");
|
|
||||||
address.setAlias("Chris");
|
|
||||||
ctx.addContact(contact);
|
|
||||||
```
|
|
||||||
to which you can send some messages
|
|
||||||
```Java
|
|
||||||
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();
|
|
||||||
```
|
|
20
_config.yml
Normal file
20
_config.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Welcome to Jekyll!
|
||||||
|
#
|
||||||
|
# This config file is meant for settings that affect your whole blog, values
|
||||||
|
# which you are expected to set up once and rarely need to edit after that.
|
||||||
|
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
||||||
|
# 'jekyll serve'. If you change this file, please restart the server process.
|
||||||
|
|
||||||
|
# Site settings
|
||||||
|
title: Jabit
|
||||||
|
email: chrigu.meyer@gmail.com
|
||||||
|
description: > # this means to ignore newlines until "baseurl:"
|
||||||
|
You want to create an awesome Bitmessage app using Jabit? Then you've come
|
||||||
|
to the right place!
|
||||||
|
baseurl: "/Jabit" # the subpath of your site, e.g. /blog
|
||||||
|
url: "http://dissem.github.io" # the base hostname & protocol for your site
|
||||||
|
# twitter_username: jekyllrb
|
||||||
|
github_username: Dissem
|
||||||
|
|
||||||
|
# Build settings
|
||||||
|
markdown: kramdown
|
29
_drafts/2016-06-22-project-setup.markdown
Normal file
29
_drafts/2016-06-22-project-setup.markdown
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: "Setting up Jabit for Your Project"
|
||||||
|
date: 2016-06-22 00:01:00 +0200
|
||||||
|
categories: setup
|
||||||
|
---
|
||||||
|
|
||||||
|
Add Jabit as Gradle dependency:
|
||||||
|
|
||||||
|
{% highlight groovy %}
|
||||||
|
compile 'ch.dissem.jabit:jabit-core:1.0.0'
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
Unless you want to implement your own, also add the following:
|
||||||
|
|
||||||
|
{% highlight groovy %}
|
||||||
|
compile 'ch.dissem.jabit:jabit-networking:1.0.0'
|
||||||
|
compile 'ch.dissem.jabit:jabit-repositories:1.0.0'
|
||||||
|
compile 'ch.dissem.jabit:jabit-cryptography-bouncy:1.0.0'
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
And if you want to import from or export to the Wallet Import Format (used by PyBitmessage) you might also want to add:
|
||||||
|
|
||||||
|
{% highlight groovy %}
|
||||||
|
compile 'ch.dissem.jabit:jabit-wif:1.0.0'
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
For Android clients use `jabit-cryptography-spongy` instead of `jabit-cryptography-bouncy`.
|
||||||
|
|
54
_drafts/2016-06-22-using-the-api.markdown
Normal file
54
_drafts/2016-06-22-using-the-api.markdown
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: "Using the Jabit API"
|
||||||
|
date: 2016-06-22 00:02:00 +0200
|
||||||
|
categories: setup
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
First, you'll need to create a `BitmessageContext`:
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
JdbcConfig jdbcConfig = new JdbcConfig();
|
||||||
|
BitmessageContext ctx = new BitmessageContext.Builder()
|
||||||
|
.addressRepo(new JdbcAddressRepository(jdbcConfig))
|
||||||
|
.inventory(new JdbcInventory(jdbcConfig))
|
||||||
|
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||||
|
.nodeRegistry(new MemoryNodeRegistry())
|
||||||
|
.networkHandler(new NetworkNode())
|
||||||
|
.cryptography(new BouncyCryptography())
|
||||||
|
.build();
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
This creates a simple context using a H2 database that will be created in the user's home directory. Next you'll need to
|
||||||
|
start the context and decide what happens if a message arrives:
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
ctx.startup(new BitmessageContext.Listener() {
|
||||||
|
@Override
|
||||||
|
public void receive(Plaintext plaintext) {
|
||||||
|
// TODO: Notify the user
|
||||||
|
}
|
||||||
|
});
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
Then you might want to create an identity
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
BitmessageAddress identity = ctx.createIdentity(false, Pubkey.Feature.DOES_ACK);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
or add some contacts
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
BitmessageAddress contact = new BitmessageAddress("BM-2cTarrmjMdRicKZ4qQ8A13JhoR3Uq6Zh5j");
|
||||||
|
address.setAlias("Chris");
|
||||||
|
ctx.addContact(contact);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
to which you can send some messages
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
ctx.send(identity, contact, "Test", "Hello Chris, this is a message.");
|
||||||
|
{% endhighlight %}
|
38
_includes/footer.html
Normal file
38
_includes/footer.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<footer class="site-footer">
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
|
||||||
|
<h2 class="footer-heading">{{ site.title }}</h2>
|
||||||
|
|
||||||
|
<div class="footer-col-wrapper">
|
||||||
|
<div class="footer-col footer-col-1">
|
||||||
|
<ul class="contact-list">
|
||||||
|
<li>{{ site.title }}</li>
|
||||||
|
<li><a href="mailto:{{ site.email }}">{{ site.email }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-col footer-col-2">
|
||||||
|
<ul class="social-media-list">
|
||||||
|
{% if site.github_username %}
|
||||||
|
<li>
|
||||||
|
{% include icon-github.html username=site.github_username %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if site.twitter_username %}
|
||||||
|
<li>
|
||||||
|
{% include icon-twitter.html username=site.twitter_username %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-col footer-col-3">
|
||||||
|
<p>{{ site.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</footer>
|
12
_includes/head.html
Normal file
12
_includes/head.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
|
||||||
|
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}">
|
||||||
|
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}">
|
||||||
|
</head>
|
28
_includes/header.html
Normal file
28
_includes/header.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<header class="site-header">
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
|
||||||
|
<a class="site-title" href="{{ site.baseurl }}/">{{ site.title }}</a>
|
||||||
|
|
||||||
|
<nav class="site-nav">
|
||||||
|
<a href="#" class="menu-icon">
|
||||||
|
<svg viewBox="0 0 18 15">
|
||||||
|
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
|
||||||
|
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
|
||||||
|
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="trigger">
|
||||||
|
{% assign sorted_pages = site.pages | sort: 'order' %}
|
||||||
|
{% for my_page in sorted_pages %}
|
||||||
|
{% if my_page.title %}
|
||||||
|
<a class="page-link" href="{{ my_page.url | prepend: site.baseurl }}">{{ my_page.title }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</header>
|
1
_includes/icon-github.html
Normal file
1
_includes/icon-github.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<a href="https://github.com/{{ include.username }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">{{ include.username }}</span></a>
|
1
_includes/icon-github.svg
Normal file
1
_includes/icon-github.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 16 16"><path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/></svg>
|
After Width: | Height: | Size: 926 B |
1
_includes/icon-twitter.html
Normal file
1
_includes/icon-twitter.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<a href="https://twitter.com/{{ include.username }}"><span class="icon icon--twitter">{% include icon-twitter.svg %}</span><span class="username">{{ include.username }}</span></a>
|
1
_includes/icon-twitter.svg
Normal file
1
_includes/icon-twitter.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 16 16"><path fill="#828282" d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z"/></svg>
|
After Width: | Height: | Size: 787 B |
20
_layouts/default.html
Normal file
20
_layouts/default.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
{% include head.html %}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% include header.html %}
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="wrapper">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include footer.html %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
14
_layouts/page.html
Normal file
14
_layouts/page.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
<article class="post">
|
||||||
|
|
||||||
|
<header class="post-header">
|
||||||
|
<h1 class="post-title">{{ page.title }}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</article>
|
15
_layouts/post.html
Normal file
15
_layouts/post.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
|
||||||
|
|
||||||
|
<header class="post-header">
|
||||||
|
<h1 class="post-title" itemprop="name headline">{{ page.title }}</h1>
|
||||||
|
<p class="post-meta"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time>{% if page.author %} • <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">{{ page.author }}</span></span>{% endif %}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="post-content" itemprop="articleBody">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</article>
|
212
_sass/_base.scss
Normal file
212
_sass/_base.scss
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* Reset some basic elements
|
||||||
|
*/
|
||||||
|
body, h1, h2, h3, h4, h5, h6,
|
||||||
|
p, blockquote, pre, hr,
|
||||||
|
dl, dd, ol, ul, figure {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic styling
|
||||||
|
*/
|
||||||
|
body {
|
||||||
|
font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family;
|
||||||
|
color: $text-color;
|
||||||
|
background-color: $background-color;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-font-feature-settings: "kern" 1;
|
||||||
|
-moz-font-feature-settings: "kern" 1;
|
||||||
|
-o-font-feature-settings: "kern" 1;
|
||||||
|
font-feature-settings: "kern" 1;
|
||||||
|
font-kerning: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set `margin-bottom` to maintain vertical rhythm
|
||||||
|
*/
|
||||||
|
h1, h2, h3, h4, h5, h6,
|
||||||
|
p, blockquote, pre,
|
||||||
|
ul, ol, dl, figure,
|
||||||
|
%vertical-rhythm {
|
||||||
|
margin-bottom: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Images
|
||||||
|
*/
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures
|
||||||
|
*/
|
||||||
|
figure > img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
font-size: $small-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists
|
||||||
|
*/
|
||||||
|
ul, ol {
|
||||||
|
margin-left: $spacing-unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
> ul,
|
||||||
|
> ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Headings
|
||||||
|
*/
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: $base-font-weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links
|
||||||
|
*/
|
||||||
|
a {
|
||||||
|
color: $brand-color;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: darken($brand-color, 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $text-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blockquotes
|
||||||
|
*/
|
||||||
|
blockquote {
|
||||||
|
color: $grey-color;
|
||||||
|
border-left: 4px solid $grey-color-light;
|
||||||
|
padding-left: $spacing-unit / 2;
|
||||||
|
font-size: 18px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
> :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
color: $text-color;
|
||||||
|
background-color: $blue-color-light;
|
||||||
|
border-left: 4px solid $blue-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code formatting
|
||||||
|
*/
|
||||||
|
pre,
|
||||||
|
code {
|
||||||
|
font-size: 15px;
|
||||||
|
border: 1px solid $grey-color-light;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #eef;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 8px 12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
> code {
|
||||||
|
border: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper
|
||||||
|
*/
|
||||||
|
.wrapper {
|
||||||
|
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
|
||||||
|
max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: $spacing-unit;
|
||||||
|
padding-left: $spacing-unit;
|
||||||
|
@extend %clearfix;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
|
||||||
|
max-width: calc(#{$content-width} - (#{$spacing-unit}));
|
||||||
|
padding-right: $spacing-unit / 2;
|
||||||
|
padding-left: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clearfix
|
||||||
|
*/
|
||||||
|
%clearfix {
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icons
|
||||||
|
*/
|
||||||
|
.icon {
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: $grey-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
242
_sass/_layout.scss
Normal file
242
_sass/_layout.scss
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* Site header
|
||||||
|
*/
|
||||||
|
.site-header {
|
||||||
|
border-top: 5px solid $grey-color-dark;
|
||||||
|
border-bottom: 1px solid $grey-color-light;
|
||||||
|
min-height: 56px;
|
||||||
|
|
||||||
|
// Positioning context for the mobile navigation icon
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 56px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:visited {
|
||||||
|
color: $grey-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-nav {
|
||||||
|
float: right;
|
||||||
|
line-height: 56px;
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
color: $text-color;
|
||||||
|
line-height: $base-line-height;
|
||||||
|
|
||||||
|
// Gaps between nav items, but not on the last one
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-query($on-palm) {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
right: $spacing-unit / 2;
|
||||||
|
background-color: $background-color;
|
||||||
|
border: 1px solid $grey-color-light;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
width: 36px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 0;
|
||||||
|
padding-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 15px;
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: $grey-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
clear: both;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .trigger {
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Site footer
|
||||||
|
*/
|
||||||
|
.site-footer {
|
||||||
|
border-top: 1px solid $grey-color-light;
|
||||||
|
padding: $spacing-unit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-heading {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-list,
|
||||||
|
.social-media-list {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-wrapper {
|
||||||
|
font-size: 15px;
|
||||||
|
color: $grey-color;
|
||||||
|
margin-left: -$spacing-unit / 2;
|
||||||
|
@extend %clearfix;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col {
|
||||||
|
float: left;
|
||||||
|
margin-bottom: $spacing-unit / 2;
|
||||||
|
padding-left: $spacing-unit / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-1 {
|
||||||
|
width: -webkit-calc(35% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(35% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-2 {
|
||||||
|
width: -webkit-calc(20% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(20% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-3 {
|
||||||
|
width: -webkit-calc(45% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(45% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
.footer-col-1,
|
||||||
|
.footer-col-2 {
|
||||||
|
width: -webkit-calc(50% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(50% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-col-3 {
|
||||||
|
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-query($on-palm) {
|
||||||
|
.footer-col {
|
||||||
|
float: none;
|
||||||
|
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
width: calc(100% - (#{$spacing-unit} / 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page content
|
||||||
|
*/
|
||||||
|
.page-content {
|
||||||
|
padding: $spacing-unit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-heading {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-list {
|
||||||
|
margin-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
margin-bottom: $spacing-unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-meta {
|
||||||
|
font-size: $small-font-size;
|
||||||
|
color: $grey-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-link {
|
||||||
|
display: block;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts
|
||||||
|
*/
|
||||||
|
.post-header {
|
||||||
|
margin-bottom: $spacing-unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title {
|
||||||
|
font-size: 42px;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content {
|
||||||
|
margin-bottom: $spacing-unit;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 26px;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
@include media-query($on-laptop) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
_sass/_syntax-highlighting.scss
Normal file
71
_sass/_syntax-highlighting.scss
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Syntax highlighting styles
|
||||||
|
*/
|
||||||
|
.highlight {
|
||||||
|
background: #fff;
|
||||||
|
@extend %vertical-rhythm;
|
||||||
|
|
||||||
|
.highlighter-rouge & {
|
||||||
|
background: #eef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c { color: #998; font-style: italic } // Comment
|
||||||
|
.err { color: #a61717; background-color: #e3d2d2 } // Error
|
||||||
|
.k { font-weight: bold } // Keyword
|
||||||
|
.o { font-weight: bold } // Operator
|
||||||
|
.cm { color: #998; font-style: italic } // Comment.Multiline
|
||||||
|
.cp { color: #999; font-weight: bold } // Comment.Preproc
|
||||||
|
.c1 { color: #998; font-style: italic } // Comment.Single
|
||||||
|
.cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
|
||||||
|
.gd { color: #000; background-color: #fdd } // Generic.Deleted
|
||||||
|
.gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
|
||||||
|
.ge { font-style: italic } // Generic.Emph
|
||||||
|
.gr { color: #a00 } // Generic.Error
|
||||||
|
.gh { color: #999 } // Generic.Heading
|
||||||
|
.gi { color: #000; background-color: #dfd } // Generic.Inserted
|
||||||
|
.gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
|
||||||
|
.go { color: #888 } // Generic.Output
|
||||||
|
.gp { color: #555 } // Generic.Prompt
|
||||||
|
.gs { font-weight: bold } // Generic.Strong
|
||||||
|
.gu { color: #aaa } // Generic.Subheading
|
||||||
|
.gt { color: #a00 } // Generic.Traceback
|
||||||
|
.kc { font-weight: bold } // Keyword.Constant
|
||||||
|
.kd { font-weight: bold } // Keyword.Declaration
|
||||||
|
.kp { font-weight: bold } // Keyword.Pseudo
|
||||||
|
.kr { font-weight: bold } // Keyword.Reserved
|
||||||
|
.kt { color: #458; font-weight: bold } // Keyword.Type
|
||||||
|
.m { color: #099 } // Literal.Number
|
||||||
|
.s { color: #d14 } // Literal.String
|
||||||
|
.na { color: #008080 } // Name.Attribute
|
||||||
|
.nb { color: #0086B3 } // Name.Builtin
|
||||||
|
.nc { color: #458; font-weight: bold } // Name.Class
|
||||||
|
.no { color: #008080 } // Name.Constant
|
||||||
|
.ni { color: #800080 } // Name.Entity
|
||||||
|
.ne { color: #900; font-weight: bold } // Name.Exception
|
||||||
|
.nf { color: #900; font-weight: bold } // Name.Function
|
||||||
|
.nn { color: #555 } // Name.Namespace
|
||||||
|
.nt { color: #000080 } // Name.Tag
|
||||||
|
.nv { color: #008080 } // Name.Variable
|
||||||
|
.ow { font-weight: bold } // Operator.Word
|
||||||
|
.w { color: #bbb } // Text.Whitespace
|
||||||
|
.mf { color: #099 } // Literal.Number.Float
|
||||||
|
.mh { color: #099 } // Literal.Number.Hex
|
||||||
|
.mi { color: #099 } // Literal.Number.Integer
|
||||||
|
.mo { color: #099 } // Literal.Number.Oct
|
||||||
|
.sb { color: #d14 } // Literal.String.Backtick
|
||||||
|
.sc { color: #d14 } // Literal.String.Char
|
||||||
|
.sd { color: #d14 } // Literal.String.Doc
|
||||||
|
.s2 { color: #d14 } // Literal.String.Double
|
||||||
|
.se { color: #d14 } // Literal.String.Escape
|
||||||
|
.sh { color: #d14 } // Literal.String.Heredoc
|
||||||
|
.si { color: #d14 } // Literal.String.Interpol
|
||||||
|
.sx { color: #d14 } // Literal.String.Other
|
||||||
|
.sr { color: #009926 } // Literal.String.Regex
|
||||||
|
.s1 { color: #d14 } // Literal.String.Single
|
||||||
|
.ss { color: #990073 } // Literal.String.Symbol
|
||||||
|
.bp { color: #999 } // Name.Builtin.Pseudo
|
||||||
|
.vc { color: #008080 } // Name.Variable.Class
|
||||||
|
.vg { color: #008080 } // Name.Variable.Global
|
||||||
|
.vi { color: #008080 } // Name.Variable.Instance
|
||||||
|
.il { color: #099 } // Literal.Number.Integer.Long
|
||||||
|
}
|
12
about.md
Normal file
12
about.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: About
|
||||||
|
permalink: /about/
|
||||||
|
order: 1000
|
||||||
|
---
|
||||||
|
|
||||||
|
Jabit is a Bitmessage library for Java. It aims to be easy to use, so you can create great Bitmessage apps.
|
||||||
|
|
||||||
|
You can find the source code for the Jabit at:
|
||||||
|
{% include icon-github.html username="Dissem" %} /
|
||||||
|
[Jabit](https://github.com/Dissem/Jabit)
|
35
api.md
Normal file
35
api.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: "API"
|
||||||
|
permalink: "/api"
|
||||||
|
order: 200
|
||||||
|
categories: api
|
||||||
|
---
|
||||||
|
> See [Jabit's JavaDoc](http://www.javadoc.io/doc/ch.dissem.jabit/jabit-core/1.0.1) for a detailed documentation of all classes and methods. Feel free to either open an issue or create a pull request if you find something to be unclear or incorrect.
|
||||||
|
{: .info}
|
||||||
|
|
||||||
|
# Context Setup
|
||||||
|
The `BitmessageContext` provides all methods a usual Bitmessage client needs. Note that all its methods should be thread safe, but you'll need to check your `BitmessageContext.Listener` for thread safety. The listener may even be called simultaneously from different threads.
|
||||||
|
|
||||||
|
To keep the library small, flexible and independent from (but compatible to) heavy-duty frameworks such as Spring, the `BitmessageContext` needs to be initialized with all its dependencies.
|
||||||
|
|
||||||
|
# Creating Identities
|
||||||
|
|
||||||
|
{% highlight java %}
|
||||||
|
BitmessageAddress identity = ctx.createIdentity(false, Pubkey.Feature.DOES_ACK);
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
If _shorter_ is enabled, Jabit searches a private key that yields a slightly shorter address. This is not optimized and may take a long time to complete.
|
||||||
|
|
||||||
|
# Importing Identities
|
||||||
|
Identities can be imported from PyBitmessages keys.dat by using the WifImporter from the library `jabit-wif`. The constructor needs a `BitmessageContext` and either a file, a string representation of a keys.dat or an input stream. The importer will throw an exception if the data can't be parsed.
|
||||||
|
|
||||||
|
Once constructed you can choose to list the contained identities, import all, a subset or a specific identity into the given `BitmessageContext`.
|
||||||
|
|
||||||
|
# Exporting Identities
|
||||||
|
Exports work very similar to imports. A `WifExporter` also needs a `BitmessageContext` to construct. You then can add either all or specific identities to the importer. Id you provide specific identities, make sure they have a private key. They will not be reloaded.
|
||||||
|
|
||||||
|
If you're done adding identities, call one of the `write` methods to save the exported identities, or `toString()` for the string representation.
|
||||||
|
|
||||||
|
# Threads
|
||||||
|
> TODO
|
90
build.gradle
90
build.gradle
@ -1,90 +0,0 @@
|
|||||||
subprojects {
|
|
||||||
apply plugin: 'java'
|
|
||||||
apply plugin: 'maven'
|
|
||||||
apply plugin: 'signing'
|
|
||||||
apply plugin: 'jacoco'
|
|
||||||
apply plugin: 'gitflow-version'
|
|
||||||
|
|
||||||
sourceCompatibility = 1.7
|
|
||||||
group = 'ch.dissem.jabit'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
testLogging {
|
|
||||||
exceptionFormat = 'full'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task javadocJar(type: Jar) {
|
|
||||||
classifier = 'javadoc'
|
|
||||||
from javadoc
|
|
||||||
}
|
|
||||||
|
|
||||||
task sourcesJar(type: Jar) {
|
|
||||||
classifier = 'sources'
|
|
||||||
from sourceSets.main.allSource
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts {
|
|
||||||
archives javadocJar, sourcesJar
|
|
||||||
}
|
|
||||||
|
|
||||||
signing {
|
|
||||||
required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 }
|
|
||||||
sign configurations.archives
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadArchives {
|
|
||||||
repositories {
|
|
||||||
mavenDeployer {
|
|
||||||
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
|
|
||||||
|
|
||||||
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
|
|
||||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
|
|
||||||
authentication(userName: ossrhUsername, password: ossrhPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
pom.project {
|
|
||||||
name 'Jabit'
|
|
||||||
packaging 'jar'
|
|
||||||
url 'https://github.com/Dissem/Jabit'
|
|
||||||
|
|
||||||
scm {
|
|
||||||
connection 'scm:git:https://github.com/Dissem/Jabit.git'
|
|
||||||
developerConnection 'scm:git:git@github.com:Dissem/Jabit.git'
|
|
||||||
url 'https://github.com/Dissem/Jabit.git'
|
|
||||||
}
|
|
||||||
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name 'The Apache License, Version 2.0'
|
|
||||||
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
developers {
|
|
||||||
developer {
|
|
||||||
name 'Christian Basler'
|
|
||||||
email 'chrigu.meyer@gmail.com'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jacocoTestReport {
|
|
||||||
reports {
|
|
||||||
xml.enabled = true
|
|
||||||
html.enabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check.dependsOn jacocoTestReport
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
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') << {
|
|
||||||
println "Version deduced from git: '${project.version}'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
implementation-class=ch.dissem.gradle.GitFlowVersion
|
|
@ -1,32 +0,0 @@
|
|||||||
uploadArchives {
|
|
||||||
repositories {
|
|
||||||
mavenDeployer {
|
|
||||||
pom.project {
|
|
||||||
name 'Jabit Core'
|
|
||||||
artifactId = 'jabit-core'
|
|
||||||
description 'A Java implementation of the Bitmessage protocol. This is the core part. You\'ll either need the networking and repositories modules, too, or implement your own.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
testArtifacts.extendsFrom testRuntime
|
|
||||||
}
|
|
||||||
|
|
||||||
task testJar(type: Jar) {
|
|
||||||
classifier = 'test'
|
|
||||||
from sourceSets.test.output
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts {
|
|
||||||
testArtifacts testJar
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
|
||||||
testCompile 'junit:junit:4.12'
|
|
||||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
|
||||||
testCompile project(':cryptography-bc')
|
|
||||||
}
|
|
@ -1,451 +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.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.TTL;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
|
|
||||||
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 InternalContext ctx;
|
|
||||||
|
|
||||||
private final Labeler labeler;
|
|
||||||
|
|
||||||
private final boolean sendPubkeyOnIdentityCreation;
|
|
||||||
|
|
||||||
private BitmessageContext(Builder builder) {
|
|
||||||
if (builder.listener instanceof Listener.WithContext) {
|
|
||||||
((Listener.WithContext) builder.listener).setContext(this);
|
|
||||||
}
|
|
||||||
ctx = new InternalContext(builder);
|
|
||||||
labeler = builder.labeler;
|
|
||||||
ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
|
|
||||||
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressRepository addresses() {
|
|
||||||
return ctx.getAddressRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageRepository messages() {
|
|
||||||
return ctx.getMessageRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Labeler labeler() {
|
|
||||||
return labeler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
|
|
||||||
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
|
||||||
shorter,
|
|
||||||
ctx.getStreams()[0],
|
|
||||||
NETWORK_NONCE_TRIALS_PER_BYTE,
|
|
||||||
NETWORK_EXTRA_BYTES,
|
|
||||||
features
|
|
||||||
));
|
|
||||||
ctx.getAddressRepository().save(identity);
|
|
||||||
if (sendPubkeyOnIdentityCreation) {
|
|
||||||
ctx.sendPubkey(identity, identity.getStream());
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress joinChan(String passphrase, String address) {
|
|
||||||
BitmessageAddress chan = BitmessageAddress.chan(address, passphrase);
|
|
||||||
chan.setAlias(passphrase);
|
|
||||||
ctx.getAddressRepository().save(chan);
|
|
||||||
return chan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress createChan(String passphrase) {
|
|
||||||
// FIXME: hardcoded stream number
|
|
||||||
BitmessageAddress chan = BitmessageAddress.chan(1, passphrase);
|
|
||||||
ctx.getAddressRepository().save(chan);
|
|
||||||
return chan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<BitmessageAddress> createDeterministicAddresses(
|
|
||||||
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
|
|
||||||
List<BitmessageAddress> result = BitmessageAddress.deterministic(
|
|
||||||
passphrase, numberOfAddresses, version, stream, shorter);
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
BitmessageAddress address = result.get(i);
|
|
||||||
address.setAlias("deterministic (" + (i + 1) + ")");
|
|
||||||
ctx.getAddressRepository().save(address);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
.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.");
|
|
||||||
}
|
|
||||||
labeler().markAsSending(msg);
|
|
||||||
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) {
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (to == null || to.getPubkey() != null) {
|
|
||||||
LOG.info("Sending message.");
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
if (msg.getType() == MSG) {
|
|
||||||
ctx.send(msg);
|
|
||||||
} else {
|
|
||||||
ctx.send(
|
|
||||||
msg.getFrom(),
|
|
||||||
to,
|
|
||||||
Factory.getBroadcast(msg),
|
|
||||||
msg.getTTL()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startup() {
|
|
||||||
ctx.getNetworkHandler().start();
|
|
||||||
}
|
|
||||||
|
|
||||||
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, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes expired objects from the inventory. You should call this method regularly,
|
|
||||||
* e.g. daily and on each shutdown.
|
|
||||||
*/
|
|
||||||
public void cleanup() {
|
|
||||||
ctx.getInventory().cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends messages again whose time to live expired without being acknowledged. (And whose
|
|
||||||
* recipient is expected to send acknowledgements.
|
|
||||||
* <p>
|
|
||||||
* You should call this method regularly, but be aware of the following:
|
|
||||||
* <ul>
|
|
||||||
* <li>As messages might be sent, POW will be done. It is therefore not advised to
|
|
||||||
* call it on shutdown.</li>
|
|
||||||
* <li>It shouldn't be called right after startup, as it's possible the missing
|
|
||||||
* acknowledgement was sent while the client was offline.</li>
|
|
||||||
* <li>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.</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public void resendUnacknowledgedMessages() {
|
|
||||||
ctx.resendUnacknowledged();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
// 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.
|
|
||||||
ctx.getNetworkListener().receive(object);
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property status() {
|
|
||||||
return new Property("status", null,
|
|
||||||
ctx.getNetworkHandler().getNetworkStatus(),
|
|
||||||
new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message listener that needs a {@link BitmessageContext}, i.e. for implementing some sort of chat bot.
|
|
||||||
*/
|
|
||||||
interface WithContext extends Listener {
|
|
||||||
void setContext(BitmessageContext ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
int port = 8444;
|
|
||||||
Inventory inventory;
|
|
||||||
NodeRegistry nodeRegistry;
|
|
||||||
NetworkHandler networkHandler;
|
|
||||||
AddressRepository addressRepo;
|
|
||||||
MessageRepository messageRepo;
|
|
||||||
ProofOfWorkRepository proofOfWorkRepository;
|
|
||||||
ProofOfWorkEngine proofOfWorkEngine;
|
|
||||||
Cryptography cryptography;
|
|
||||||
CustomCommandHandler customCommandHandler;
|
|
||||||
Labeler labeler;
|
|
||||||
Listener listener;
|
|
||||||
int connectionLimit = 150;
|
|
||||||
long connectionTTL = 30 * MINUTE;
|
|
||||||
boolean sendPubkeyOnIdentityCreation = true;
|
|
||||||
|
|
||||||
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 customCommandHandler(CustomCommandHandler handler) {
|
|
||||||
this.customCommandHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
|
|
||||||
this.proofOfWorkEngine = proofOfWorkEngine;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder labeler(Labeler labeler) {
|
|
||||||
this.labeler = labeler;
|
|
||||||
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>
|
|
||||||
*
|
|
||||||
* @deprecated use {@link TTL#pubkey(long)} instead.
|
|
||||||
*/
|
|
||||||
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 (labeler == null) {
|
|
||||||
labeler = new DefaultLabeler();
|
|
||||||
}
|
|
||||||
if (customCommandHandler == null) {
|
|
||||||
customCommandHandler = new CustomCommandHandler() {
|
|
||||||
@Override
|
|
||||||
public MessagePayload handle(CustomMessage request) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"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,187 +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.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.ports.Labeler;
|
|
||||||
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.PUBKEY_REQUESTED;
|
|
||||||
|
|
||||||
class DefaultMessageListener implements NetworkHandler.MessageListener, InternalContext.ContextHolder {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class);
|
|
||||||
private final Labeler labeler;
|
|
||||||
private final BitmessageContext.Listener listener;
|
|
||||||
private InternalContext ctx;
|
|
||||||
|
|
||||||
public DefaultMessageListener(Labeler labeler, BitmessageContext.Listener listener) {
|
|
||||||
this.labeler = labeler;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext context) {
|
|
||||||
this.ctx = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
public void receive(ObjectMessage object) throws IOException {
|
|
||||||
ObjectPayload payload = object.getPayload();
|
|
||||||
if (payload.getType() == null) {
|
|
||||||
if (payload instanceof GenericPayload) {
|
|
||||||
receive((GenericPayload) payload);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new IllegalArgumentException("Unknown payload type " + payload.getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
|
|
||||||
BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
|
|
||||||
if (identity != null && identity.getPrivateKey() != null && !identity.isChan()) {
|
|
||||||
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(PUBKEY_REQUESTED, address);
|
|
||||||
LOG.info("Sending " + messages.size() + " messages for contact " + address);
|
|
||||||
for (Plaintext msg : messages) {
|
|
||||||
ctx.getLabeler().markAsSending(msg);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
ctx.send(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(ObjectMessage object, Msg msg) throws IOException {
|
|
||||||
for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
|
|
||||||
try {
|
|
||||||
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
|
|
||||||
Plaintext plaintext = msg.getPlaintext();
|
|
||||||
plaintext.setTo(identity);
|
|
||||||
if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) {
|
|
||||||
LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
|
|
||||||
} else {
|
|
||||||
receive(object.getInventoryVector(), plaintext);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(GenericPayload ack) {
|
|
||||||
if (ack.getData().length == Msg.ACK_LENGTH) {
|
|
||||||
Plaintext msg = ctx.getMessageRepository().getMessageForAck(ack.getData());
|
|
||||||
if (msg != null) {
|
|
||||||
ctx.getLabeler().markAsAcknowledged(msg);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
receive(object.getInventoryVector(), broadcast.getPlaintext());
|
|
||||||
}
|
|
||||||
} catch (DecryptionFailedException ignore) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void receive(InventoryVector iv, Plaintext msg) {
|
|
||||||
msg.setInventoryVector(iv);
|
|
||||||
labeler.setLabels(msg);
|
|
||||||
ctx.getMessageRepository().save(msg);
|
|
||||||
listener.receive(msg);
|
|
||||||
updatePubkey(msg.getFrom(), msg.getFrom().getPubkey());
|
|
||||||
|
|
||||||
if (msg.getType() == Plaintext.Type.MSG && msg.getTo().has(Pubkey.Feature.DOES_ACK)) {
|
|
||||||
ObjectMessage ack = msg.getAckMessage();
|
|
||||||
if (ack != null) {
|
|
||||||
ctx.getInventory().storeObject(ack);
|
|
||||||
ctx.getNetworkHandler().offer(ack.getInventoryVector());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,317 +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.*;
|
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
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.List;
|
|
||||||
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);
|
|
||||||
|
|
||||||
public final static long NETWORK_NONCE_TRIALS_PER_BYTE = 1000;
|
|
||||||
public final static long NETWORK_EXTRA_BYTES = 1000;
|
|
||||||
|
|
||||||
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 CustomCommandHandler customCommandHandler;
|
|
||||||
private final ProofOfWorkService proofOfWorkService;
|
|
||||||
private final Labeler labeler;
|
|
||||||
private final NetworkHandler.MessageListener networkListener;
|
|
||||||
|
|
||||||
private final TreeSet<Long> streams = new TreeSet<>();
|
|
||||||
private final int port;
|
|
||||||
private final long clientNonce;
|
|
||||||
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.customCommandHandler = builder.customCommandHandler;
|
|
||||||
this.port = builder.port;
|
|
||||||
this.connectionLimit = builder.connectionLimit;
|
|
||||||
this.connectionTTL = builder.connectionTTL;
|
|
||||||
this.labeler = builder.labeler;
|
|
||||||
this.networkListener = new DefaultMessageListener(labeler, builder.listener);
|
|
||||||
|
|
||||||
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, customCommandHandler, builder.labeler,
|
|
||||||
networkListener);
|
|
||||||
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 Labeler getLabeler() {
|
|
||||||
return labeler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkHandler.MessageListener getNetworkListener() {
|
|
||||||
return networkListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 void send(final Plaintext plaintext) {
|
|
||||||
if (plaintext.getAckMessage() != null) {
|
|
||||||
long expires = UnixTime.now(+plaintext.getTTL());
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
proofOfWorkService.doProofOfWorkWithAck(plaintext, expires);
|
|
||||||
} else {
|
|
||||||
send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), plaintext.getTTL());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
|
|
||||||
final long timeToLive) {
|
|
||||||
try {
|
|
||||||
final BitmessageAddress recipient = (to != null ? to : from);
|
|
||||||
long expires = UnixTime.now(+timeToLive);
|
|
||||||
LOG.info("Expires at " + expires);
|
|
||||||
final ObjectMessage object = new ObjectMessage.Builder()
|
|
||||||
.stream(recipient.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(recipient.getPubkey());
|
|
||||||
}
|
|
||||||
proofOfWorkService.doProofOfWork(to, object);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(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()));
|
|
||||||
// TODO: remember that the pubkey is just about to be sent, and on which stream!
|
|
||||||
proofOfWorkService.doProofOfWork(response);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(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();
|
|
||||||
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 void resendUnacknowledged() {
|
|
||||||
List<Plaintext> messages = messageRepository.findMessagesToResend();
|
|
||||||
for (Plaintext message : messages) {
|
|
||||||
send(message);
|
|
||||||
messageRepository.save(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,125 +0,0 @@
|
|||||||
package ch.dissem.bitmessage;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.*;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Msg;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.ports.Cryptography;
|
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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(long delayInMilliseconds) {
|
|
||||||
final List<byte[]> items = powRepo.getItems();
|
|
||||||
if (items.isEmpty()) return;
|
|
||||||
|
|
||||||
// Wait for 30 seconds, to let the application start up before putting heavy load on the CPU
|
|
||||||
new Timer().schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
LOG.info("Doing POW for " + items.size() + " tasks.");
|
|
||||||
for (byte[] initialHash : items) {
|
|
||||||
Item item = powRepo.getItem(initialHash);
|
|
||||||
cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes,
|
|
||||||
ProofOfWorkService.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, delayInMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ? NETWORK_NONCE_TRIALS_PER_BYTE : pubkey.getNonceTrialsPerByte();
|
|
||||||
long extraBytes = pubkey == null ? NETWORK_EXTRA_BYTES : 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) {
|
|
||||||
final ObjectMessage ack = plaintext.getAckMessage();
|
|
||||||
messageRepo.save(plaintext);
|
|
||||||
Item item = new 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
|
|
||||||
public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
|
|
||||||
Item item = powRepo.getItem(initialHash);
|
|
||||||
if (item.message == null) {
|
|
||||||
ObjectMessage object = powRepo.getItem(initialHash).object;
|
|
||||||
object.setNonce(nonce);
|
|
||||||
Plaintext plaintext = messageRepo.getMessage(initialHash);
|
|
||||||
if (plaintext != null) {
|
|
||||||
plaintext.setInventoryVector(object.getInventoryVector());
|
|
||||||
plaintext.updateNextTry();
|
|
||||||
ctx.getLabeler().markAsSent(plaintext);
|
|
||||||
messageRepo.save(plaintext);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
ctx.getNetworkListener().receive(object);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.debug(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
ctx.getInventory().storeObject(object);
|
|
||||||
ctx.getNetworkHandler().offer(object.getInventoryVector());
|
|
||||||
} else {
|
|
||||||
item.message.getAckMessage().setNonce(nonce);
|
|
||||||
final ObjectMessage object = new ObjectMessage.Builder()
|
|
||||||
.stream(item.message.getStream())
|
|
||||||
.expiresTime(item.expirationTime)
|
|
||||||
.payload(new Msg(item.message))
|
|
||||||
.build();
|
|
||||||
if (object.isSigned()) {
|
|
||||||
object.sign(item.message.getFrom().getPrivateKey());
|
|
||||||
}
|
|
||||||
if (object.getPayload() instanceof Encrypted) {
|
|
||||||
object.encrypt(item.message.getTo().getPubkey());
|
|
||||||
}
|
|
||||||
doProofOfWork(item.message.getTo(), object);
|
|
||||||
}
|
|
||||||
powRepo.removeObject(initialHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.cryptography = cryptography();
|
|
||||||
this.powRepo = ctx.getProofOfWorkRepository();
|
|
||||||
this.messageRepo = ctx.getMessageRepository();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +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.nio.ByteBuffer;
|
|
||||||
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 static final long serialVersionUID = -5117688017050138720L;
|
|
||||||
|
|
||||||
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 out) throws IOException {
|
|
||||||
Encode.varInt(addresses.size(), out);
|
|
||||||
for (NetworkAddress address : addresses) {
|
|
||||||
address.write(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
Encode.varInt(addresses.size(), buffer);
|
|
||||||
for (NetworkAddress address : addresses) {
|
|
||||||
address.write(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<NetworkAddress> addresses = new ArrayList<>();
|
|
||||||
|
|
||||||
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,277 +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.Pubkey.Feature;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
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.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
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.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 static final long serialVersionUID = 2386328540805994064L;
|
|
||||||
|
|
||||||
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;
|
|
||||||
private boolean chan;
|
|
||||||
|
|
||||||
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 = 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
|
|
||||||
byte[] 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
|
|
||||||
int offset = Bytes.numberOfLeadingZeros(ripe);
|
|
||||||
os.write(ripe, offset, ripe.length - offset);
|
|
||||||
byte[] checksum = cryptography().doubleSha512(os.toByteArray());
|
|
||||||
os.write(checksum, 0, 4);
|
|
||||||
this.address = "BM-" + Base58.encode(os.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(Pubkey publicKey) {
|
|
||||||
this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
|
|
||||||
this.pubkey = publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BitmessageAddress(String address, String passphrase) {
|
|
||||||
this(address);
|
|
||||||
this.privateKey = new PrivateKey(this, passphrase);
|
|
||||||
this.pubkey = this.privateKey.getPubkey();
|
|
||||||
if (!Arrays.equals(ripe, privateKey.getPubkey().getRipe())) {
|
|
||||||
throw new IllegalArgumentException("Wrong address or passphrase");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BitmessageAddress chan(String address, String passphrase) {
|
|
||||||
BitmessageAddress result = new BitmessageAddress(address, passphrase);
|
|
||||||
result.chan = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BitmessageAddress chan(long stream, String passphrase) {
|
|
||||||
PrivateKey privateKey = new PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase);
|
|
||||||
BitmessageAddress result = new BitmessageAddress(privateKey);
|
|
||||||
result.chan = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<BitmessageAddress> deterministic(String passphrase, int numberOfAddresses,
|
|
||||||
long version, long stream, boolean shorter) {
|
|
||||||
List<BitmessageAddress> result = new ArrayList<>(numberOfAddresses);
|
|
||||||
List<PrivateKey> privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter);
|
|
||||||
for (PrivateKey pk : privateKeys) {
|
|
||||||
result.add(new BitmessageAddress(pk));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = cryptography().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 = 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);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(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(cryptography().doubleSha512(out.toByteArray()), 32, 64);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(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 ? address : alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isChan() {
|
|
||||||
return chan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChan(boolean chan) {
|
|
||||||
this.chan = chan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean has(Feature feature) {
|
|
||||||
if (pubkey == null || feature == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return feature.isActive(pubkey.getBehaviorBitfield());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +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.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
|
||||||
import static ch.dissem.bitmessage.utils.Decode.varString;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class CustomMessage implements MessagePayload {
|
|
||||||
private static final long serialVersionUID = -8932056829480326011L;
|
|
||||||
|
|
||||||
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 ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (data != null) {
|
|
||||||
Encode.varString(command, out);
|
|
||||||
out.write(data);
|
|
||||||
} else {
|
|
||||||
throw new ApplicationException("Tried to write custom message without data. " +
|
|
||||||
"Programmer: did you forget to override #write()?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
if (data != null) {
|
|
||||||
Encode.varString(command, buffer);
|
|
||||||
buffer.put(data);
|
|
||||||
} else {
|
|
||||||
throw new ApplicationException("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 ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +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.exception.DecryptionFailedException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for objects that have encrypted content
|
|
||||||
*/
|
|
||||||
public interface Encrypted {
|
|
||||||
void encrypt(byte[] publicKey) throws IOException;
|
|
||||||
|
|
||||||
void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException;
|
|
||||||
|
|
||||||
boolean isDecrypted();
|
|
||||||
}
|
|
@ -1,84 +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.nio.ByteBuffer;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'getdata' command is used to request objects from a node.
|
|
||||||
*/
|
|
||||||
public class GetData implements MessagePayload {
|
|
||||||
private static final long serialVersionUID = 1433878785969631061L;
|
|
||||||
|
|
||||||
public static final int MAX_INVENTORY_SIZE = 50_000;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
Encode.varInt(inventory.size(), buffer);
|
|
||||||
for (InventoryVector iv : inventory) {
|
|
||||||
iv.write(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<InventoryVector> inventory = new LinkedList<>();
|
|
||||||
|
|
||||||
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,82 +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.nio.ByteBuffer;
|
|
||||||
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 static final long serialVersionUID = 3662992522956947145L;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
Encode.varInt(inventory.size(), buffer);
|
|
||||||
for (InventoryVector iv : inventory) {
|
|
||||||
iv.write(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private List<InventoryVector> inventory = new LinkedList<>();
|
|
||||||
|
|
||||||
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,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.entity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A command can hold a network message payload
|
|
||||||
*/
|
|
||||||
public interface MessagePayload extends Streamable {
|
|
||||||
Command getCommand();
|
|
||||||
|
|
||||||
enum Command {
|
|
||||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +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.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A network message is exchanged between two nodes.
|
|
||||||
*/
|
|
||||||
public class NetworkMessage implements Streamable {
|
|
||||||
private static final long serialVersionUID = 702708857104464809L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 = cryptography().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');
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] 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.length, out);
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
try {
|
|
||||||
out.write(getChecksum(payloadBytes));
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
*/
|
|
||||||
public ByteBuffer writeHeaderAndGetPayloadBuffer(ByteBuffer headerBuffer) {
|
|
||||||
return ByteBuffer.wrap(writeHeader(headerBuffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For improved memory efficiency, you should use {@link #writeHeaderAndGetPayloadBuffer(ByteBuffer)}
|
|
||||||
* 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
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
byte[] payloadBytes = writeHeader(buffer);
|
|
||||||
buffer.put(payloadBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] writeHeader(ByteBuffer out) {
|
|
||||||
// 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();
|
|
||||||
try {
|
|
||||||
out.put(command.getBytes("ASCII"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
for (int i = command.length(); i < 12; i++) {
|
|
||||||
out.put((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] 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.length, out);
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
try {
|
|
||||||
out.put(getChecksum(payloadBytes));
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// message payload
|
|
||||||
return payloadBytes;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,271 +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.ApplicationException;
|
|
||||||
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 java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'object' command sends an object that is shared throughout the network.
|
|
||||||
*/
|
|
||||||
public class ObjectMessage implements MessagePayload {
|
|
||||||
private static final long serialVersionUID = 2495752480120659139L;
|
|
||||||
|
|
||||||
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 > 0 ? builder.streamNumber : builder.payload.getStream();
|
|
||||||
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(cryptography().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 ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sign(PrivateKey key) {
|
|
||||||
if (payload.isSigned()) {
|
|
||||||
payload.setSignature(cryptography().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 ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
|
|
||||||
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
|
|
||||||
return cryptography().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
if (nonce == null) {
|
|
||||||
out.write(new byte[8]);
|
|
||||||
} else {
|
|
||||||
out.write(nonce);
|
|
||||||
}
|
|
||||||
out.write(getPayloadBytesWithoutNonce());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
if (nonce == null) {
|
|
||||||
buffer.put(new byte[8]);
|
|
||||||
} else {
|
|
||||||
buffer.put(nonce);
|
|
||||||
}
|
|
||||||
buffer.put(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 ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private byte[] nonce;
|
|
||||||
private long expiresTime;
|
|
||||||
private long objectType = -1;
|
|
||||||
private long streamNumber;
|
|
||||||
private ObjectPayload payload;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
ObjectMessage that = (ObjectMessage) o;
|
|
||||||
|
|
||||||
return expiresTime == that.expiresTime &&
|
|
||||||
objectType == that.objectType &&
|
|
||||||
version == that.version &&
|
|
||||||
stream == that.stream &&
|
|
||||||
Objects.equals(payload, that.payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = Arrays.hashCode(nonce);
|
|
||||||
result = 31 * result + (int) (expiresTime ^ (expiresTime >>> 32));
|
|
||||||
result = 31 * result + (int) (objectType ^ (objectType >>> 32));
|
|
||||||
result = 31 * result + (int) (version ^ (version >>> 32));
|
|
||||||
result = 31 * result + (int) (stream ^ (stream >>> 32));
|
|
||||||
result = 31 * result + (payload != null ? payload.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,612 +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.Msg;
|
|
||||||
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.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.utils.*;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The unencrypted message to be sent by 'msg' or 'broadcast'.
|
|
||||||
*/
|
|
||||||
public class Plaintext implements Streamable {
|
|
||||||
private static final long serialVersionUID = -5325729856394951079L;
|
|
||||||
|
|
||||||
private final Type type;
|
|
||||||
private final BitmessageAddress from;
|
|
||||||
private final long encoding;
|
|
||||||
private final byte[] message;
|
|
||||||
private final byte[] ackData;
|
|
||||||
private ObjectMessage ackMessage;
|
|
||||||
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 long ttl;
|
|
||||||
private int retries;
|
|
||||||
private Long nextTry;
|
|
||||||
|
|
||||||
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;
|
|
||||||
ackData = builder.ackData;
|
|
||||||
if (builder.ackMessage != null && builder.ackMessage.length > 0) {
|
|
||||||
ackMessage = Factory.getObjectMessage(
|
|
||||||
3,
|
|
||||||
new ByteArrayInputStream(builder.ackMessage),
|
|
||||||
builder.ackMessage.length);
|
|
||||||
}
|
|
||||||
signature = builder.signature;
|
|
||||||
status = builder.status;
|
|
||||||
sent = builder.sent;
|
|
||||||
received = builder.received;
|
|
||||||
labels = builder.labels;
|
|
||||||
ttl = builder.ttl;
|
|
||||||
retries = builder.retries;
|
|
||||||
nextTry = builder.nextTry;
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
.ackMessage(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 IllegalStateException("Correct address already set");
|
|
||||||
if (!Arrays.equals(this.to.getRipe(), to.getRipe())) {
|
|
||||||
throw new IllegalArgumentException("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) {
|
|
||||||
if (to.has(Feature.DOES_ACK) && getAckMessage() != null) {
|
|
||||||
ByteArrayOutputStream ack = new ByteArrayOutputStream();
|
|
||||||
getAckMessage().write(ack);
|
|
||||||
Encode.varBytes(ack.toByteArray(), out);
|
|
||||||
} else {
|
|
||||||
Encode.varInt(0, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (includeSignature) {
|
|
||||||
if (signature == null) {
|
|
||||||
Encode.varInt(0, out);
|
|
||||||
} else {
|
|
||||||
Encode.varInt(signature.length, out);
|
|
||||||
out.write(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(ByteBuffer buffer, boolean includeSignature) {
|
|
||||||
Encode.varInt(from.getVersion(), buffer);
|
|
||||||
Encode.varInt(from.getStream(), buffer);
|
|
||||||
Encode.int32(from.getPubkey().getBehaviorBitfield(), buffer);
|
|
||||||
buffer.put(from.getPubkey().getSigningKey(), 1, 64);
|
|
||||||
buffer.put(from.getPubkey().getEncryptionKey(), 1, 64);
|
|
||||||
if (from.getVersion() >= 3) {
|
|
||||||
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), buffer);
|
|
||||||
Encode.varInt(from.getPubkey().getExtraBytes(), buffer);
|
|
||||||
}
|
|
||||||
if (type == Type.MSG) {
|
|
||||||
buffer.put(to.getRipe());
|
|
||||||
}
|
|
||||||
Encode.varInt(encoding, buffer);
|
|
||||||
Encode.varInt(message.length, buffer);
|
|
||||||
buffer.put(message);
|
|
||||||
if (type == Type.MSG) {
|
|
||||||
if (to.has(Feature.DOES_ACK) && getAckMessage() != null) {
|
|
||||||
Encode.varBytes(Encode.bytes(getAckMessage()), buffer);
|
|
||||||
} else {
|
|
||||||
Encode.varInt(0, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (includeSignature) {
|
|
||||||
if (signature == null) {
|
|
||||||
Encode.varInt(0, buffer);
|
|
||||||
} else {
|
|
||||||
Encode.varInt(signature.length, buffer);
|
|
||||||
buffer.put(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
write(out, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
write(buffer, 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) {
|
|
||||||
if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) {
|
|
||||||
sent = UnixTime.now();
|
|
||||||
}
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTTL() {
|
|
||||||
return ttl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRetries() {
|
|
||||||
return retries;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getNextTry() {
|
|
||||||
return nextTry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateNextTry() {
|
|
||||||
if (to != null) {
|
|
||||||
if (nextTry == null) {
|
|
||||||
if (sent != null && to.has(Feature.DOES_ACK)) {
|
|
||||||
nextTry = UnixTime.now(+ttl);
|
|
||||||
retries++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nextTry = nextTry + (1 << retries) * ttl;
|
|
||||||
retries++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ApplicationException(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) &&
|
|
||||||
Objects.equals(getAckMessage(), plaintext.getAckMessage()) &&
|
|
||||||
Arrays.equals(to == null ? null : to.getRipe(), plaintext.to == null ? null : 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, ackData, 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) {
|
|
||||||
for (Label label : labels) {
|
|
||||||
this.labels.add(label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeLabel(Label.Type type) {
|
|
||||||
Iterator<Label> iterator = labels.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
Label label = iterator.next();
|
|
||||||
if (label.getType() == type) {
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getAckData() {
|
|
||||||
return ackData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectMessage getAckMessage() {
|
|
||||||
if (ackMessage == null) {
|
|
||||||
ackMessage = Factory.createAck(this);
|
|
||||||
}
|
|
||||||
return ackMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
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[] ackData;
|
|
||||||
private byte[] ackMessage;
|
|
||||||
private byte[] signature;
|
|
||||||
private long sent;
|
|
||||||
private long received;
|
|
||||||
private Status status;
|
|
||||||
private Set<Label> labels = new HashSet<>();
|
|
||||||
private long ttl;
|
|
||||||
private int retries;
|
|
||||||
private Long nextTry;
|
|
||||||
|
|
||||||
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 ApplicationException(e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder message(byte[] message) {
|
|
||||||
this.message = message;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ackMessage(byte[] ack) {
|
|
||||||
if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ackMessage only allowed for msg");
|
|
||||||
this.ackMessage = ack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ackData(byte[] ackData) {
|
|
||||||
if (type != Type.MSG && ackData != null)
|
|
||||||
throw new IllegalArgumentException("ackMessage only allowed for msg");
|
|
||||||
this.ackData = ackData;
|
|
||||||
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 Builder ttl(long ttl) {
|
|
||||||
this.ttl = ttl;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder retries(int retries) {
|
|
||||||
this.retries = retries;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder nextTry(Long nextTry) {
|
|
||||||
this.nextTry = nextTry;
|
|
||||||
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 && destinationRipe != null) {
|
|
||||||
to = new BitmessageAddress(0, 0, destinationRipe);
|
|
||||||
}
|
|
||||||
if (type == Type.MSG && ackMessage == null && ackData == null) {
|
|
||||||
ackData = cryptography().randomBytes(Msg.ACK_LENGTH);
|
|
||||||
}
|
|
||||||
if (ttl <= 0) {
|
|
||||||
ttl = TTL.msg();
|
|
||||||
}
|
|
||||||
return new Plaintext(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +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;
|
|
||||||
|
|
||||||
public interface PlaintextHolder {
|
|
||||||
Plaintext getPlaintext();
|
|
||||||
}
|
|
@ -1,31 +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 java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object that can be written to an {@link OutputStream}
|
|
||||||
*/
|
|
||||||
public interface Streamable extends Serializable {
|
|
||||||
void write(OutputStream stream) throws IOException;
|
|
||||||
|
|
||||||
void write(ByteBuffer buffer);
|
|
||||||
}
|
|
@ -1,43 +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 java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
|
||||||
*/
|
|
||||||
public class VerAck implements MessagePayload {
|
|
||||||
private static final long serialVersionUID = -4302074845199181687L;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Command getCommand() {
|
|
||||||
return Command.VERACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
// 'verack' doesn't have any payload, so there is nothing to write
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,214 +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.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
|
||||||
*/
|
|
||||||
public class Version implements MessagePayload {
|
|
||||||
private static final long serialVersionUID = 7219240857343176567L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 defaults(long clientNonce) {
|
|
||||||
version = BitmessageContext.CURRENT_VERSION;
|
|
||||||
services = 1;
|
|
||||||
timestamp = UnixTime.now();
|
|
||||||
userAgent = "/Jabit:0.0.1/";
|
|
||||||
streamNumbers = new long[]{1};
|
|
||||||
nonce = clientNonce;
|
|
||||||
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,114 +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 java.util.Objects;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
private static final long serialVersionUID = 4064521827582239069L;
|
|
||||||
|
|
||||||
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(cryptography().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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Broadcast broadcast = (Broadcast) o;
|
|
||||||
return stream == broadcast.stream &&
|
|
||||||
(Objects.equals(encrypted, broadcast.encrypted) || Objects.equals(plaintext, broadcast.plaintext));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(stream);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,214 +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.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.utils.*;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
|
|
||||||
public class CryptoBox implements Streamable {
|
|
||||||
private static final long serialVersionUID = 7217659539975573852L;
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
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 = cryptography().randomBytes(16);
|
|
||||||
|
|
||||||
// 3. Generate a new random EC key pair with private key called r and public key called R.
|
|
||||||
byte[] 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.
|
|
||||||
byte[] P = cryptography().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 = cryptography().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 = 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 CryptoBox(Builder builder) {
|
|
||||||
initializationVector = builder.initializationVector;
|
|
||||||
curveType = builder.curveType;
|
|
||||||
R = cryptography().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 = cryptography().multiply(R, k);
|
|
||||||
// 3. Use the X component of public key P and calculate the SHA512 hash H.
|
|
||||||
byte[] 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.
|
|
||||||
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(cryptography().crypt(false, encrypted, key_e, initializationVector));
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] calculateMac(byte[] key_m) {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream macData = new ByteArrayOutputStream();
|
|
||||||
writeWithoutMAC(macData);
|
|
||||||
return cryptography().mac(key_m, macData.toByteArray());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeCoordinateComponent(ByteBuffer buffer, byte[] x) {
|
|
||||||
int offset = Bytes.numberOfLeadingZeros(x);
|
|
||||||
int length = x.length - offset;
|
|
||||||
Encode.int16(length, buffer);
|
|
||||||
buffer.put(x, offset, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
writeWithoutMAC(stream);
|
|
||||||
stream.write(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
buffer.put(initializationVector);
|
|
||||||
Encode.int16(curveType, buffer);
|
|
||||||
writeCoordinateComponent(buffer, Points.getX(R));
|
|
||||||
writeCoordinateComponent(buffer, Points.getY(R));
|
|
||||||
buffer.put(encrypted);
|
|
||||||
buffer.put(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,88 +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.nio.ByteBuffer;
|
|
||||||
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 static final long serialVersionUID = -912314085064185940L;
|
|
||||||
|
|
||||||
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, long stream, InputStream is, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream stream) throws IOException {
|
|
||||||
stream.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
buffer.put(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,82 +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;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for a public key.
|
|
||||||
*/
|
|
||||||
public class GetPubkey extends ObjectPayload {
|
|
||||||
private static final long serialVersionUID = -3634516646972610180L;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
buffer.put(ripeTag);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,135 +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 java.nio.ByteBuffer;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for person-to-person messages.
|
|
||||||
*/
|
|
||||||
public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
|
|
||||||
private static final long serialVersionUID = 4327495048296365733L;
|
|
||||||
public static final int ACK_LENGTH = 32;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
|
|
||||||
encrypted.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
if (!super.equals(o)) return false;
|
|
||||||
Msg msg = (Msg) o;
|
|
||||||
return stream == msg.stream &&
|
|
||||||
(Objects.equals(encrypted, msg.encrypted) || Objects.equals(plaintext, msg.plaintext));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return (int) stream;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The payload of an 'object' command. This is shared by the network.
|
|
||||||
*/
|
|
||||||
public abstract class ObjectPayload implements Streamable {
|
|
||||||
private static final long serialVersionUID = -5034977402902364482L;
|
|
||||||
|
|
||||||
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,44 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
|
|
||||||
*/
|
|
||||||
public enum ObjectType {
|
|
||||||
GET_PUBKEY(0),
|
|
||||||
PUBKEY(1),
|
|
||||||
MSG(2),
|
|
||||||
BROADCAST(3);
|
|
||||||
|
|
||||||
int number;
|
|
||||||
|
|
||||||
ObjectType(int 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,121 +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.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
|
||||||
*/
|
|
||||||
public abstract class Pubkey extends ObjectPayload {
|
|
||||||
private static final long serialVersionUID = -6634533361454999619L;
|
|
||||||
|
|
||||||
public final static long LATEST_VERSION = 4;
|
|
||||||
|
|
||||||
protected Pubkey(long version) {
|
|
||||||
super(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
|
|
||||||
return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract byte[] getSigningKey();
|
|
||||||
|
|
||||||
public abstract byte[] getEncryptionKey();
|
|
||||||
|
|
||||||
public abstract int getBehaviorBitfield();
|
|
||||||
|
|
||||||
public byte[] getRipe() {
|
|
||||||
return cryptography().ripemd160(cryptography().sha512(getSigningKey(), getEncryptionKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNonceTrialsPerByte() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExtraBytes() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
|
||||||
write(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeUnencrypted(ByteBuffer buffer){
|
|
||||||
write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(30),
|
|
||||||
/**
|
|
||||||
* If true, the receiving node does send acknowledgements (rather than dropping them).
|
|
||||||
*/
|
|
||||||
DOES_ACK(31);
|
|
||||||
|
|
||||||
private int bit;
|
|
||||||
|
|
||||||
Feature(int bitNumber) {
|
|
||||||
// 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
|
|
||||||
this.bit = 1 << (31 - bitNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isActive(int bitfield) {
|
|
||||||
return (bitfield & bit) != 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +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.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A version 2 public key.
|
|
||||||
*/
|
|
||||||
public class V2Pubkey extends Pubkey {
|
|
||||||
private static final long serialVersionUID = -257598690676510460L;
|
|
||||||
|
|
||||||
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 out) throws IOException {
|
|
||||||
Encode.int32(behaviorBitfield, out);
|
|
||||||
out.write(publicSigningKey, 1, 64);
|
|
||||||
out.write(publicEncryptionKey, 1, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
Encode.int32(behaviorBitfield, buffer);
|
|
||||||
buffer.put(publicSigningKey, 1, 64);
|
|
||||||
buffer.put(publicEncryptionKey, 1, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private long streamNumber;
|
|
||||||
private int behaviorBitfield;
|
|
||||||
private byte[] publicSigningKey;
|
|
||||||
private byte[] publicEncryptionKey;
|
|
||||||
|
|
||||||
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,167 +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 {
|
|
||||||
private static final long serialVersionUID = 6958853116648528319L;
|
|
||||||
|
|
||||||
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 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,67 +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;
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
public class V4Broadcast extends Broadcast {
|
|
||||||
private static final long serialVersionUID = 195663108282762711L;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
encrypted.write(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,191 +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.nio.ByteBuffer;
|
|
||||||
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 static final long serialVersionUID = 1556710353694033093L;
|
|
||||||
|
|
||||||
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 write(ByteBuffer buffer) {
|
|
||||||
buffer.put(tag);
|
|
||||||
encrypted.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeUnencrypted(OutputStream out) throws IOException {
|
|
||||||
decrypted.write(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeUnencrypted(ByteBuffer buffer) {
|
|
||||||
decrypted.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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,66 +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 static final long serialVersionUID = 920649721626968644L;
|
|
||||||
|
|
||||||
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,73 +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.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class InventoryVector implements Streamable, Serializable {
|
|
||||||
private static final long serialVersionUID = -7349009673063348719L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 ? 0 : Arrays.hashCode(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getHash() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryVector(byte[] hash) {
|
|
||||||
this.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream out) throws IOException {
|
|
||||||
out.write(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
buffer.put(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Strings.hex(hash).toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +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 static final long serialVersionUID = 831782893630994914L;
|
|
||||||
|
|
||||||
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,238 +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.exception.ApplicationException;
|
|
||||||
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.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A node's address. It's written in IPv6 format.
|
|
||||||
*/
|
|
||||||
public class NetworkAddress implements Streamable {
|
|
||||||
private static final long serialVersionUID = 2500120578167100300L;
|
|
||||||
|
|
||||||
private long time;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream number for this node
|
|
||||||
*/
|
|
||||||
private final long stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* same service(s) listed in version
|
|
||||||
*/
|
|
||||||
private final 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 final byte[] ipv6;
|
|
||||||
private final 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 ApplicationException(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 out, boolean light) throws IOException {
|
|
||||||
if (!light) {
|
|
||||||
Encode.int64(time, out);
|
|
||||||
Encode.int32(stream, out);
|
|
||||||
}
|
|
||||||
Encode.int64(services, out);
|
|
||||||
out.write(ipv6);
|
|
||||||
Encode.int16(port, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
write(buffer, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(ByteBuffer buffer, boolean light) {
|
|
||||||
if (!light) {
|
|
||||||
Encode.int64(time, buffer);
|
|
||||||
Encode.int32(stream, buffer);
|
|
||||||
}
|
|
||||||
Encode.int64(services, buffer);
|
|
||||||
buffer.put(ipv6);
|
|
||||||
Encode.int16(port, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private long time;
|
|
||||||
private long stream;
|
|
||||||
private long services = 1;
|
|
||||||
private byte[] ipv6;
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
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 Builder address(SocketAddress address) {
|
|
||||||
if (address instanceof InetSocketAddress) {
|
|
||||||
InetSocketAddress inetAddress = (InetSocketAddress) address;
|
|
||||||
ip(inetAddress.getAddress());
|
|
||||||
port(inetAddress.getPort());
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unknown type of address: " + address.getClass());
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkAddress build() {
|
|
||||||
if (time == 0) {
|
|
||||||
time = UnixTime.now();
|
|
||||||
}
|
|
||||||
return new NetworkAddress(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,198 +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.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 java.io.*;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
|
|
||||||
* {@link Pubkey} object.
|
|
||||||
*/
|
|
||||||
public class PrivateKey implements Streamable {
|
|
||||||
private static final long serialVersionUID = 8562555470709110558L;
|
|
||||||
|
|
||||||
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 = cryptography().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE);
|
|
||||||
pubSK = cryptography().createPublicKey(privSK);
|
|
||||||
pubEK = cryptography().createPublicKey(privEK);
|
|
||||||
ripe = Pubkey.getRipe(pubSK, pubEK);
|
|
||||||
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
|
|
||||||
this.privateSigningKey = privSK;
|
|
||||||
this.privateEncryptionKey = privEK;
|
|
||||||
this.pubkey = cryptography().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(BitmessageAddress address, String passphrase) {
|
|
||||||
this(address.getVersion(), address.getStream(), passphrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey(long version, long stream, String passphrase) {
|
|
||||||
this(new Builder(version, stream, false).seed(passphrase).generate());
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKey(Builder builder) {
|
|
||||||
this.privateSigningKey = builder.privSK;
|
|
||||||
this.privateEncryptionKey = builder.privEK;
|
|
||||||
this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK, builder.pubEK,
|
|
||||||
InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Builder {
|
|
||||||
final long version;
|
|
||||||
final long stream;
|
|
||||||
final boolean shorter;
|
|
||||||
|
|
||||||
byte[] seed;
|
|
||||||
long nextNonce;
|
|
||||||
|
|
||||||
byte[] privSK, privEK;
|
|
||||||
byte[] pubSK, pubEK;
|
|
||||||
|
|
||||||
private Builder(long version, long stream, boolean shorter) {
|
|
||||||
this.version = version;
|
|
||||||
this.stream = stream;
|
|
||||||
this.shorter = shorter;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder seed(String passphrase) {
|
|
||||||
try {
|
|
||||||
seed = passphrase.getBytes("UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Builder generate() {
|
|
||||||
long signingKeyNonce = nextNonce;
|
|
||||||
long encryptionKeyNonce = nextNonce + 1;
|
|
||||||
byte[] ripe;
|
|
||||||
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] != 0 || (shorter && ripe[1] != 0));
|
|
||||||
nextNonce = signingKeyNonce;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<PrivateKey> deterministic(String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
|
|
||||||
List<PrivateKey> result = new ArrayList<>(numberOfAddresses);
|
|
||||||
Builder builder = new Builder(version, stream, shorter).seed(passphrase);
|
|
||||||
for (int i = 0; i < numberOfAddresses; i++) {
|
|
||||||
builder.generate();
|
|
||||||
result.add(new PrivateKey(builder));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ByteBuffer buffer) {
|
|
||||||
Encode.varInt(pubkey.getVersion(), buffer);
|
|
||||||
Encode.varInt(pubkey.getStream(), buffer);
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
pubkey.writeUnencrypted(baos);
|
|
||||||
Encode.varBytes(baos.toByteArray(), buffer);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
Encode.varBytes(privateSigningKey, buffer);
|
|
||||||
Encode.varBytes(privateEncryptionKey, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates an illegal Bitmessage address
|
|
||||||
*/
|
|
||||||
public class AddressFormatException extends RuntimeException {
|
|
||||||
private static final long serialVersionUID = 6943764578672021573L;
|
|
||||||
|
|
||||||
public AddressFormatException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class ApplicationException extends RuntimeException {
|
|
||||||
private static final long serialVersionUID = 1796776684126759324L;
|
|
||||||
|
|
||||||
public ApplicationException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApplicationException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +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;
|
|
||||||
|
|
||||||
public class DecryptionFailedException extends Exception {
|
|
||||||
private static final long serialVersionUID = 3241116253113872731L;
|
|
||||||
}
|
|
@ -1,30 +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 {
|
|
||||||
private static final long serialVersionUID = 9105580366564571318L;
|
|
||||||
|
|
||||||
public InsufficientProofOfWorkException(byte[] target, byte[] hash) {
|
|
||||||
super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception on the node that's severe enough to cause the client to disconnect this node.
|
|
||||||
*
|
|
||||||
* @author Ch. Basler
|
|
||||||
*/
|
|
||||||
public class NodeException extends RuntimeException {
|
|
||||||
private static final long serialVersionUID = 2965325796118227802L;
|
|
||||||
|
|
||||||
public NodeException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 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 org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Stack;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.ports.NetworkHandler.HEADER_SIZE;
|
|
||||||
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A pool for {@link ByteBuffer}s. As they may use up a lot of memory,
|
|
||||||
* they should be reused as efficiently as possible.
|
|
||||||
*/
|
|
||||||
class BufferPool {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(BufferPool.class);
|
|
||||||
|
|
||||||
public static final BufferPool bufferPool = new BufferPool();
|
|
||||||
|
|
||||||
private final Map<Integer, Stack<ByteBuffer>> pools = new TreeMap<>();
|
|
||||||
|
|
||||||
private BufferPool() {
|
|
||||||
pools.put(HEADER_SIZE, new Stack<ByteBuffer>());
|
|
||||||
pools.put(54, new Stack<ByteBuffer>());
|
|
||||||
pools.put(1000, new Stack<ByteBuffer>());
|
|
||||||
pools.put(60000, new Stack<ByteBuffer>());
|
|
||||||
pools.put(MAX_PAYLOAD_SIZE, new Stack<ByteBuffer>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized ByteBuffer allocate(int capacity) {
|
|
||||||
Integer targetSize = getTargetSize(capacity);
|
|
||||||
Stack<ByteBuffer> pool = pools.get(targetSize);
|
|
||||||
if (pool.isEmpty()) {
|
|
||||||
LOG.trace("Creating new buffer of size " + targetSize);
|
|
||||||
return ByteBuffer.allocate(targetSize);
|
|
||||||
} else {
|
|
||||||
return pool.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a buffer that has the size of the Bitmessage network message header, 24 bytes.
|
|
||||||
*
|
|
||||||
* @return a buffer of size 24
|
|
||||||
*/
|
|
||||||
public synchronized ByteBuffer allocateHeaderBuffer() {
|
|
||||||
Stack<ByteBuffer> pool = pools.get(HEADER_SIZE);
|
|
||||||
if (pool.isEmpty()) {
|
|
||||||
return ByteBuffer.allocate(HEADER_SIZE);
|
|
||||||
} else {
|
|
||||||
return pool.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void deallocate(ByteBuffer buffer) {
|
|
||||||
buffer.clear();
|
|
||||||
Stack<ByteBuffer> pool = pools.get(buffer.capacity());
|
|
||||||
if (pool == null) {
|
|
||||||
throw new IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() +
|
|
||||||
" one of " + pools.keySet() + " expected.");
|
|
||||||
} else {
|
|
||||||
pool.push(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer getTargetSize(int capacity) {
|
|
||||||
for (Integer size : pools.keySet()) {
|
|
||||||
if (size >= capacity) return size;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Requested capacity too large: " +
|
|
||||||
"requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,217 +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 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.io.InputStream;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
|
|
||||||
*/
|
|
||||||
public class Factory {
|
|
||||||
private 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(),
|
|
||||||
cryptography().createPublicKey(privateSigningKey),
|
|
||||||
cryptography().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, streamNumber, stream, 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, streamNumber, stream, 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, streamNumber, stream, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Broadcast getBroadcast(Plaintext plaintext) {
|
|
||||||
BitmessageAddress sendingAddress = plaintext.getFrom();
|
|
||||||
if (sendingAddress.getVersion() < 4) {
|
|
||||||
return new V4Broadcast(sendingAddress, plaintext);
|
|
||||||
} else {
|
|
||||||
return new V5Broadcast(sendingAddress, plaintext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ObjectMessage createAck(Plaintext plaintext) {
|
|
||||||
if (plaintext == null || plaintext.getAckData() == null)
|
|
||||||
return null;
|
|
||||||
GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData());
|
|
||||||
return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(plaintext.getTTL())).build();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,232 +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.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = cryptography().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,189 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 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.MessagePayload;
|
|
||||||
import ch.dissem.bitmessage.entity.NetworkMessage;
|
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.exception.NodeException;
|
|
||||||
import ch.dissem.bitmessage.utils.Decode;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
|
|
||||||
import static ch.dissem.bitmessage.factory.BufferPool.bufferPool;
|
|
||||||
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to the {@link V3MessageFactory}, but used for NIO buffers which may or may not contain a whole message.
|
|
||||||
*/
|
|
||||||
public class V3MessageReader {
|
|
||||||
private ByteBuffer headerBuffer;
|
|
||||||
private ByteBuffer dataBuffer;
|
|
||||||
|
|
||||||
private ReaderState state = ReaderState.MAGIC;
|
|
||||||
private String command;
|
|
||||||
private int length;
|
|
||||||
private byte[] checksum;
|
|
||||||
|
|
||||||
private List<NetworkMessage> messages = new LinkedList<>();
|
|
||||||
|
|
||||||
public ByteBuffer getActiveBuffer() {
|
|
||||||
if (state != null && state != ReaderState.DATA) {
|
|
||||||
if (headerBuffer == null) {
|
|
||||||
headerBuffer = bufferPool.allocateHeaderBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state == ReaderState.DATA ? dataBuffer : headerBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update() {
|
|
||||||
if (state != ReaderState.DATA) {
|
|
||||||
getActiveBuffer();
|
|
||||||
headerBuffer.flip();
|
|
||||||
}
|
|
||||||
switch (state) {
|
|
||||||
case MAGIC:
|
|
||||||
if (!findMagicBytes(headerBuffer)) {
|
|
||||||
headerBuffer.compact();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state = ReaderState.HEADER;
|
|
||||||
case HEADER:
|
|
||||||
if (headerBuffer.remaining() < 20) {
|
|
||||||
headerBuffer.compact();
|
|
||||||
headerBuffer.limit(20);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
command = getCommand(headerBuffer);
|
|
||||||
length = (int) Decode.uint32(headerBuffer);
|
|
||||||
if (length > MAX_PAYLOAD_SIZE) {
|
|
||||||
throw new NodeException("Payload of " + length + " bytes received, no more than " +
|
|
||||||
MAX_PAYLOAD_SIZE + " was expected.");
|
|
||||||
}
|
|
||||||
checksum = new byte[4];
|
|
||||||
headerBuffer.get(checksum);
|
|
||||||
state = ReaderState.DATA;
|
|
||||||
bufferPool.deallocate(headerBuffer);
|
|
||||||
headerBuffer = null;
|
|
||||||
dataBuffer = bufferPool.allocate(length);
|
|
||||||
dataBuffer.clear();
|
|
||||||
dataBuffer.limit(length);
|
|
||||||
case DATA:
|
|
||||||
if (dataBuffer.position() < length) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
dataBuffer.flip();
|
|
||||||
}
|
|
||||||
if (!testChecksum(dataBuffer)) {
|
|
||||||
state = ReaderState.MAGIC;
|
|
||||||
throw new NodeException("Checksum failed for message '" + command + "'");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
MessagePayload payload = V3MessageFactory.getPayload(
|
|
||||||
command,
|
|
||||||
new ByteArrayInputStream(dataBuffer.array(),
|
|
||||||
dataBuffer.arrayOffset() + dataBuffer.position(), length),
|
|
||||||
length);
|
|
||||||
if (payload != null) {
|
|
||||||
messages.add(new NetworkMessage(payload));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new NodeException(e.getMessage());
|
|
||||||
} finally {
|
|
||||||
state = ReaderState.MAGIC;
|
|
||||||
bufferPool.deallocate(dataBuffer);
|
|
||||||
dataBuffer = null;
|
|
||||||
dataBuffer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<NetworkMessage> getMessages() {
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean findMagicBytes(ByteBuffer buffer) {
|
|
||||||
int i = 0;
|
|
||||||
while (buffer.hasRemaining()) {
|
|
||||||
if (i == 0) {
|
|
||||||
buffer.mark();
|
|
||||||
}
|
|
||||||
if (buffer.get() == MAGIC_BYTES[i]) {
|
|
||||||
i++;
|
|
||||||
if (i == MAGIC_BYTES.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i > 0) {
|
|
||||||
buffer.reset();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getCommand(ByteBuffer buffer) {
|
|
||||||
int start = buffer.position();
|
|
||||||
int l = 0;
|
|
||||||
while (l < 12 && buffer.get() != 0) l++;
|
|
||||||
int i = l + 1;
|
|
||||||
while (i < 12) {
|
|
||||||
if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command");
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return new String(buffer.array(), start, l, "ASCII");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean testChecksum(ByteBuffer buffer) {
|
|
||||||
byte[] payloadChecksum = cryptography().sha512(buffer.array(),
|
|
||||||
buffer.arrayOffset() + buffer.position(), length);
|
|
||||||
for (int i = 0; i < checksum.length; i++) {
|
|
||||||
if (checksum[i] != payloadChecksum[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its
|
|
||||||
* connection is severed.
|
|
||||||
*/
|
|
||||||
public void cleanup() {
|
|
||||||
state = null;
|
|
||||||
if (headerBuffer != null) {
|
|
||||||
bufferPool.deallocate(headerBuffer);
|
|
||||||
}
|
|
||||||
if (dataBuffer != null) {
|
|
||||||
bufferPool.deallocate(dataBuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ReaderState {MAGIC, HEADER, DATA}
|
|
||||||
}
|
|
@ -1,192 +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.ApplicationException;
|
|
||||||
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.Provider;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
|
|
||||||
import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
|
|
||||||
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 {
|
|
||||||
protected 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);
|
|
||||||
|
|
||||||
protected final Provider provider;
|
|
||||||
private InternalContext context;
|
|
||||||
|
|
||||||
protected AbstractCryptography(Provider provider) {
|
|
||||||
this.provider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] sha512(byte[] data, int offset, int length) {
|
|
||||||
MessageDigest mda = md("SHA-512");
|
|
||||||
mda.update(data, offset, length);
|
|
||||||
return mda.digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
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, NETWORK_NONCE_TRIALS_PER_BYTE);
|
|
||||||
extraBytes = max(extraBytes, NETWORK_EXTRA_BYTES);
|
|
||||||
|
|
||||||
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 = NETWORK_NONCE_TRIALS_PER_BYTE;
|
|
||||||
if (extraBytes == 0) extraBytes = NETWORK_EXTRA_BYTES;
|
|
||||||
|
|
||||||
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
|
|
||||||
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(TWO_POW_64.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 ApplicationException(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 ApplicationException(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,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 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.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.utils.Strings;
|
|
||||||
import ch.dissem.bitmessage.utils.UnixTime;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.SqlStrings.join;
|
|
||||||
|
|
||||||
public abstract class AbstractMessageRepository implements MessageRepository, InternalContext.ContextHolder {
|
|
||||||
protected InternalContext ctx;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext context) {
|
|
||||||
this.ctx = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void safeSenderIfNecessary(Plaintext message) {
|
|
||||||
if (message.getId() == null) {
|
|
||||||
BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(message.getFrom().getAddress());
|
|
||||||
if (savedAddress == null) {
|
|
||||||
ctx.getAddressRepository().save(message.getFrom());
|
|
||||||
} else if (savedAddress.getPubkey() == null && message.getFrom().getPubkey() != null) {
|
|
||||||
savedAddress.setPubkey(message.getFrom().getPubkey());
|
|
||||||
ctx.getAddressRepository().save(savedAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plaintext getMessage(Object id) {
|
|
||||||
if (id instanceof Long) {
|
|
||||||
return single(find("id=" + id));
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Long expected for ID");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plaintext getMessage(byte[] initialHash) {
|
|
||||||
return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Plaintext getMessageForAck(byte[] ackData) {
|
|
||||||
return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Plaintext> findMessages(Label label) {
|
|
||||||
if (label == null) {
|
|
||||||
return find("id NOT IN (SELECT message_id FROM Message_Label)");
|
|
||||||
} else {
|
|
||||||
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) {
|
|
||||||
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Plaintext> findMessages(Plaintext.Status status) {
|
|
||||||
return find("status='" + status.name() + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Plaintext> findMessages(BitmessageAddress sender) {
|
|
||||||
return find("sender='" + sender.getAddress() + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Plaintext> findMessagesToResend() {
|
|
||||||
return find("status='" + Plaintext.Status.SENT.name() + "'" +
|
|
||||||
" AND next_try < " + UnixTime.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Label> getLabels() {
|
|
||||||
return findLabels("1=1");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Label> getLabels(Label.Type... types) {
|
|
||||||
return findLabels("type IN (" + join(types) + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract List<Label> findLabels(String where);
|
|
||||||
|
|
||||||
|
|
||||||
protected <T> T single(Collection<T> collection) {
|
|
||||||
switch (collection.size()) {
|
|
||||||
case 0:
|
|
||||||
return null;
|
|
||||||
case 1:
|
|
||||||
return collection.iterator().next();
|
|
||||||
default:
|
|
||||||
throw new ApplicationException("This shouldn't happen, found " + collection.size() +
|
|
||||||
" items, one or none was expected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract List<Plaintext> find(String where);
|
|
||||||
}
|
|
@ -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.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();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all subscribed chans.
|
|
||||||
*/
|
|
||||||
List<BitmessageAddress> getChans();
|
|
||||||
|
|
||||||
List<BitmessageAddress> getSubscriptions();
|
|
||||||
|
|
||||||
List<BitmessageAddress> getSubscriptions(long broadcastVersion);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all Bitmessage addresses that have no private key or are chans.
|
|
||||||
*/
|
|
||||||
List<BitmessageAddress> getContacts();
|
|
||||||
|
|
||||||
void save(BitmessageAddress address);
|
|
||||||
|
|
||||||
void remove(BitmessageAddress address);
|
|
||||||
|
|
||||||
BitmessageAddress getAddress(String address);
|
|
||||||
}
|
|
@ -1,222 +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.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
|
|
||||||
* which should be secure enough.
|
|
||||||
*/
|
|
||||||
public interface Cryptography {
|
|
||||||
/**
|
|
||||||
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
|
||||||
* success on the same thread.
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @param offset of the data to be hashed
|
|
||||||
* @param length of the data to be hashed
|
|
||||||
* @return SHA-512 hash of data within the given range
|
|
||||||
*/
|
|
||||||
byte[] sha512(byte[] data, int offset, int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
|
||||||
* success on the same thread.
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return SHA-512 hash of data
|
|
||||||
*/
|
|
||||||
byte[] sha512(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
|
|
||||||
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
|
||||||
* success on the same thread.
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return SHA-512 hash of data
|
|
||||||
*/
|
|
||||||
byte[] doubleSha512(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
|
|
||||||
* to use for the hash calculation.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
|
|
||||||
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @param length number of bytes to be taken into account
|
|
||||||
* @return SHA-512 hash of data
|
|
||||||
*/
|
|
||||||
byte[] doubleSha512(byte[] data, int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
|
|
||||||
* concatenation of all arrays, but might perform better.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
|
||||||
* order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return RIPEMD-160 hash of data
|
|
||||||
*/
|
|
||||||
byte[] ripemd160(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
|
|
||||||
* to use for the hash calculation.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
|
||||||
* order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @param length number of bytes to be taken into account
|
|
||||||
* @return SHA-256 hash of data
|
|
||||||
*/
|
|
||||||
byte[] doubleSha256(byte[] data, int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
|
|
||||||
* concatenation of all arrays, but might perform better.
|
|
||||||
* <p>
|
|
||||||
* Please note that a new {@link MessageDigest} object is created at
|
|
||||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
|
||||||
* order on the same thread.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param data to get hashed
|
|
||||||
* @return SHA hash of data
|
|
||||||
*/
|
|
||||||
byte[] sha1(byte[]... data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param length number of bytes to return
|
|
||||||
* @return an array of the given size containing random bytes
|
|
||||||
*/
|
|
||||||
byte[] randomBytes(int length);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
|
|
||||||
* live.
|
|
||||||
*
|
|
||||||
* @param object to do the proof of work for
|
|
||||||
* @param nonceTrialsPerByte difficulty
|
|
||||||
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
|
|
||||||
* @param callback to handle nonce once it's calculated
|
|
||||||
*/
|
|
||||||
void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
|
||||||
long extraBytes, ProofOfWorkEngine.Callback callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param object to be checked
|
|
||||||
* @param nonceTrialsPerByte difficulty
|
|
||||||
* @param extraBytes bytes to add to the object size
|
|
||||||
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
|
|
||||||
*/
|
|
||||||
void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
byte[] getInitialHash(ObjectMessage object);
|
|
||||||
|
|
||||||
byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the MAC for a message (data)
|
|
||||||
*
|
|
||||||
* @param key_m the symmetric key used
|
|
||||||
* @param data the message data to calculate the MAC for
|
|
||||||
* @return the MAC
|
|
||||||
*/
|
|
||||||
byte[] mac(byte[] key_m, byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param encrypt if true, encrypts data, otherwise tries to decrypt it.
|
|
||||||
* @param data
|
|
||||||
* @param key_e
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new public key fom given private keys.
|
|
||||||
*
|
|
||||||
* @param version of the public key / address
|
|
||||||
* @param stream of the address
|
|
||||||
* @param privateSigningKey private key used for signing
|
|
||||||
* @param privateEncryptionKey private key used for encryption
|
|
||||||
* @param nonceTrialsPerByte proof of work difficulty
|
|
||||||
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
|
|
||||||
* @param features of the address
|
|
||||||
* @return a public key object
|
|
||||||
*/
|
|
||||||
Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
|
||||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param privateKey private key as byte array
|
|
||||||
* @return a public key corresponding to the given private key
|
|
||||||
*/
|
|
||||||
byte[] createPublicKey(byte[] privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param privateKey private key as byte array
|
|
||||||
* @return a big integer representation (unsigned) of the given bytes
|
|
||||||
*/
|
|
||||||
BigInteger keyToBigInt(byte[] privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param data to check
|
|
||||||
* @param signature the signature of the message
|
|
||||||
* @param pubkey the sender's public key
|
|
||||||
* @return true if the signature is valid, false otherwise
|
|
||||||
*/
|
|
||||||
boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the signature of data, using the given private key.
|
|
||||||
*
|
|
||||||
* @param data to be signed
|
|
||||||
* @param privateKey to be used for signing
|
|
||||||
* @return the signature
|
|
||||||
*/
|
|
||||||
byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a random number of type long
|
|
||||||
*/
|
|
||||||
long randomNonce();
|
|
||||||
|
|
||||||
byte[] multiply(byte[] k, byte[] r);
|
|
||||||
|
|
||||||
byte[] createPoint(byte[] x, byte[] y);
|
|
||||||
}
|
|
@ -1,27 +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.CustomMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.MessagePayload;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public interface CustomCommandHandler {
|
|
||||||
MessagePayload handle(CustomMessage request);
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 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.Plaintext;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
|
||||||
|
|
||||||
public class DefaultLabeler implements Labeler, InternalContext.ContextHolder {
|
|
||||||
private InternalContext ctx;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setLabels(Plaintext msg) {
|
|
||||||
msg.setStatus(RECEIVED);
|
|
||||||
if (msg.getType() == Plaintext.Type.BROADCAST) {
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD));
|
|
||||||
} else {
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void markAsDraft(Plaintext msg) {
|
|
||||||
msg.setStatus(DRAFT);
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.DRAFT));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void markAsSending(Plaintext msg) {
|
|
||||||
if (msg.getTo() != null && msg.getTo().getPubkey() == null) {
|
|
||||||
msg.setStatus(PUBKEY_REQUESTED);
|
|
||||||
} else {
|
|
||||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
|
||||||
}
|
|
||||||
msg.removeLabel(Label.Type.DRAFT);
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void markAsSent(Plaintext msg) {
|
|
||||||
msg.setStatus(SENT);
|
|
||||||
msg.removeLabel(Label.Type.OUTBOX);
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void markAsAcknowledged(Plaintext msg) {
|
|
||||||
msg.setStatus(SENT_ACKNOWLEDGED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void markAsRead(Plaintext msg) {
|
|
||||||
msg.removeLabel(Label.Type.UNREAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void markAsUnread(Plaintext msg) {
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.UNREAD));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void delete(Plaintext msg) {
|
|
||||||
msg.getLabels().clear();
|
|
||||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.TRASH));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void archive(Plaintext msg) {
|
|
||||||
msg.getLabels().clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +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.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing.
|
|
||||||
*/
|
|
||||||
public interface Inventory {
|
|
||||||
/**
|
|
||||||
* Returns the IVs of all valid objects we have for the given streams
|
|
||||||
*/
|
|
||||||
List<InventoryVector> getInventory(long... streams);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the IVs of all objects in the offer that we don't have already. Implementations are allowed to
|
|
||||||
* ignore the streams parameter, but it must be set when calling this method.
|
|
||||||
*/
|
|
||||||
List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams);
|
|
||||||
|
|
||||||
ObjectMessage getObject(InventoryVector vector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is mainly used to search for public keys to newly added addresses or broadcasts from new
|
|
||||||
* subscriptions.
|
|
||||||
*/
|
|
||||||
List<ObjectMessage> getObjects(long stream, long version, ObjectType... types);
|
|
||||||
|
|
||||||
void storeObject(ObjectMessage object);
|
|
||||||
|
|
||||||
boolean contains(ObjectMessage object);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all objects that expired 5 minutes ago or earlier
|
|
||||||
* (so we don't accidentally request objects we just deleted)
|
|
||||||
*/
|
|
||||||
void cleanup();
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 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.Plaintext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines and sets labels. Note that it should also update the status field of a message.
|
|
||||||
* Generally it's highly advised to override the {@link DefaultLabeler} whenever possible,
|
|
||||||
* instead of directly implementing the interface.
|
|
||||||
* <p>
|
|
||||||
* As the labeler gets called whenever the state of a message changes, it can also be used
|
|
||||||
* as a listener.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public interface Labeler {
|
|
||||||
/**
|
|
||||||
* Sets the labels of a newly received message.
|
|
||||||
*
|
|
||||||
* @param msg an unlabeled message or broadcast
|
|
||||||
*/
|
|
||||||
void setLabels(Plaintext msg);
|
|
||||||
|
|
||||||
void markAsDraft(Plaintext msg);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It is paramount that this methods marks the {@link Plaintext} object with status
|
|
||||||
* {@link Plaintext.Status#PUBKEY_REQUESTED} (see {@link DefaultLabeler})
|
|
||||||
*/
|
|
||||||
void markAsSending(Plaintext msg);
|
|
||||||
|
|
||||||
void markAsSent(Plaintext msg);
|
|
||||||
|
|
||||||
void markAsAcknowledged(Plaintext msg);
|
|
||||||
|
|
||||||
void markAsRead(Plaintext msg);
|
|
||||||
|
|
||||||
void markAsUnread(Plaintext msg);
|
|
||||||
|
|
||||||
void delete(Plaintext msg);
|
|
||||||
|
|
||||||
void archive(Plaintext msg);
|
|
||||||
}
|
|
@ -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.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(Object id);
|
|
||||||
|
|
||||||
Plaintext getMessage(byte[] initialHash);
|
|
||||||
|
|
||||||
Plaintext getMessageForAck(byte[] ackData);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Label label);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Status status);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(Status status, BitmessageAddress recipient);
|
|
||||||
|
|
||||||
List<Plaintext> findMessages(BitmessageAddress sender);
|
|
||||||
|
|
||||||
List<Plaintext> findMessagesToResend();
|
|
||||||
|
|
||||||
void save(Plaintext message);
|
|
||||||
|
|
||||||
void remove(Plaintext message);
|
|
||||||
}
|
|
@ -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.exception.ApplicationException;
|
|
||||||
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.*;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
|
||||||
import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A POW engine using all available CPU cores.
|
|
||||||
*/
|
|
||||||
public class MultiThreadedPOWEngine implements ProofOfWorkEngine {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class);
|
|
||||||
private final ExecutorService waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build());
|
|
||||||
private final ExecutorService workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(final byte[] initialHash, final byte[] target, final Callback callback) {
|
|
||||||
waiterPool.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
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((byte) cores, i, initialHash, target);
|
|
||||||
workers.add(w);
|
|
||||||
}
|
|
||||||
List<Future<byte[]>> futures = new ArrayList<>(cores);
|
|
||||||
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.
|
|
||||||
futures.add(workerPool.submit(w));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
while (!Thread.interrupted()) {
|
|
||||||
for (Future<byte[]> future : futures) {
|
|
||||||
if (future.isDone()) {
|
|
||||||
callback.onNonceCalculated(initialHash, future.get());
|
|
||||||
LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds");
|
|
||||||
for (Future<byte[]> f : futures) {
|
|
||||||
f.cancel(true);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
LOG.error("POW waiter thread interrupted - this should not happen!");
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.error("POW waiter thread interrupted - this should not happen!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Worker implements Callable<byte[]> {
|
|
||||||
private final byte numberOfCores;
|
|
||||||
private final byte[] initialHash;
|
|
||||||
private final byte[] target;
|
|
||||||
private final MessageDigest mda;
|
|
||||||
private final byte[] nonce = new byte[8];
|
|
||||||
|
|
||||||
Worker(byte numberOfCores, int core, byte[] initialHash, byte[] target) {
|
|
||||||
this.numberOfCores = numberOfCores;
|
|
||||||
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 ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] call() throws Exception {
|
|
||||||
do {
|
|
||||||
inc(nonce, numberOfCores);
|
|
||||||
mda.update(nonce);
|
|
||||||
mda.update(initialHash);
|
|
||||||
if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) {
|
|
||||||
return nonce;
|
|
||||||
}
|
|
||||||
} while (!Thread.interrupted());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,86 +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.CustomMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.utils.Property;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles incoming messages
|
|
||||||
*/
|
|
||||||
public interface NetworkHandler {
|
|
||||||
int NETWORK_MAGIC_NUMBER = 8;
|
|
||||||
int HEADER_SIZE = 24;
|
|
||||||
int MAX_PAYLOAD_SIZE = 1600003;
|
|
||||||
int MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
|
|
||||||
* <p>
|
|
||||||
* An implementation should disconnect if either the timeout is reached or the returned thread is interrupted.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
Future<?> synchronize(InetAddress server, int port, long timeoutInSeconds);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
CustomMessage send(InetAddress server, int port, CustomMessage request);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a full network node, accepting incoming connections and relaying objects.
|
|
||||||
*/
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the full network node.
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Offer new objects to up to 8 random nodes.
|
|
||||||
*/
|
|
||||||
void offer(InventoryVector iv);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request each of those objects from a node that knows of the requested object.
|
|
||||||
*
|
|
||||||
* @param inventoryVectors of the objects to be requested
|
|
||||||
*/
|
|
||||||
void request(Collection<InventoryVector> inventoryVectors);
|
|
||||||
|
|
||||||
Property getNetworkStatus();
|
|
||||||
|
|
||||||
boolean isRunning();
|
|
||||||
|
|
||||||
interface MessageListener {
|
|
||||||
void receive(ObjectMessage object) throws IOException;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +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 java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores and provides known peers.
|
|
||||||
*/
|
|
||||||
public interface NodeRegistry {
|
|
||||||
List<NetworkAddress> getKnownAddresses(int limit, long... streams);
|
|
||||||
|
|
||||||
void offerAddresses(List<NetworkAddress> addresses);
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to kick start node registries.
|
|
||||||
*/
|
|
||||||
public class NodeRegistryHelper {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(NodeRegistryHelper.class);
|
|
||||||
|
|
||||||
public static Map<Long, Set<NetworkAddress>> loadStableNodes() {
|
|
||||||
try (InputStream in = NodeRegistryHelper.class.getClassLoader().getResourceAsStream("nodes.txt")) {
|
|
||||||
Scanner scanner = new Scanner(in);
|
|
||||||
long stream = 0;
|
|
||||||
Map<Long, Set<NetworkAddress>> result = new HashMap<>();
|
|
||||||
Set<NetworkAddress> streamSet = null;
|
|
||||||
while (scanner.hasNext()) {
|
|
||||||
try {
|
|
||||||
String line = scanner.nextLine().trim();
|
|
||||||
if (line.startsWith("[stream")) {
|
|
||||||
stream = Long.parseLong(line.substring(8, line.lastIndexOf(']')));
|
|
||||||
streamSet = new HashSet<>();
|
|
||||||
result.put(stream, streamSet);
|
|
||||||
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
|
|
||||||
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 : result.entrySet()) {
|
|
||||||
LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the proof of work necessary to send an object.
|
|
||||||
*/
|
|
||||||
public interface ProofOfWorkEngine {
|
|
||||||
/**
|
|
||||||
* Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long
|
|
||||||
* smaller than target.
|
|
||||||
*
|
|
||||||
* @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
|
|
||||||
* sure this is only called once.
|
|
||||||
*/
|
|
||||||
void calculateNonce(byte[] initialHash, byte[] target, Callback callback);
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
/**
|
|
||||||
* @param nonce 8 bytes nonce
|
|
||||||
*/
|
|
||||||
void onNonceCalculated(byte[] initialHash, byte[] nonce);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package ch.dissem.bitmessage.ports;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
|
|
||||||
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 putObject(Item item);
|
|
||||||
|
|
||||||
void removeObject(byte[] initialHash);
|
|
||||||
|
|
||||||
class Item {
|
|
||||||
public final ObjectMessage object;
|
|
||||||
public final long nonceTrialsPerByte;
|
|
||||||
public final long extraBytes;
|
|
||||||
|
|
||||||
// Needed for ACK POW calculation
|
|
||||||
public final Long expirationTime;
|
|
||||||
public final Plaintext message;
|
|
||||||
|
|
||||||
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
|
||||||
this(object, nonceTrialsPerByte, extraBytes, 0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, long expirationTime, Plaintext message) {
|
|
||||||
this.object = object;
|
|
||||||
this.nonceTrialsPerByte = nonceTrialsPerByte;
|
|
||||||
this.extraBytes = extraBytes;
|
|
||||||
this.expirationTime = expirationTime;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +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.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.utils.Bytes;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
try {
|
|
||||||
MessageDigest mda = MessageDigest.getInstance("SHA-512");
|
|
||||||
byte[] nonce = new byte[8];
|
|
||||||
do {
|
|
||||||
inc(nonce);
|
|
||||||
mda.update(nonce);
|
|
||||||
mda.update(initialHash);
|
|
||||||
} while (Bytes.lt(target, mda.digest(mda.digest()), 8));
|
|
||||||
callback.onNonceCalculated(initialHash, nonce);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intended to count the bytes read or written during (de-)serialization.
|
|
||||||
*/
|
|
||||||
public class AccessCounter {
|
|
||||||
private int count;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by one, if not null.
|
|
||||||
*/
|
|
||||||
public static void inc(AccessCounter counter) {
|
|
||||||
if (counter != null) counter.inc();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by length, if not null.
|
|
||||||
*/
|
|
||||||
public static void inc(AccessCounter counter, int length) {
|
|
||||||
if (counter != null) counter.inc(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by one.
|
|
||||||
*/
|
|
||||||
private void inc() {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the counter by length.
|
|
||||||
*/
|
|
||||||
private void inc(int length) {
|
|
||||||
count += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int length() {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.valueOf(count);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,168 +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 ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
|
|
||||||
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 final 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 data to encode
|
|
||||||
* @return base58 encoded input
|
|
||||||
*/
|
|
||||||
public static String encode(byte[] data) {
|
|
||||||
if (data.length == 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
final byte[] bytes = copyOfRange(data, 0, data.length);
|
|
||||||
// Count leading zeroes.
|
|
||||||
int zeroCount = 0;
|
|
||||||
while (zeroCount < bytes.length && bytes[zeroCount] == 0) {
|
|
||||||
++zeroCount;
|
|
||||||
}
|
|
||||||
// The actual encoding.
|
|
||||||
byte[] temp = new byte[bytes.length * 2];
|
|
||||||
int j = temp.length;
|
|
||||||
|
|
||||||
int startAt = zeroCount;
|
|
||||||
while (startAt < bytes.length) {
|
|
||||||
byte mod = divmod58(bytes, startAt);
|
|
||||||
if (bytes[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 ApplicationException(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 < 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,145 +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 helper class for working with byte arrays interpreted as unsigned big endian integers.
|
|
||||||
* 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
|
|
||||||
* situations.
|
|
||||||
*/
|
|
||||||
public class Bytes {
|
|
||||||
public static final byte BYTE_0x80 = (byte) 0x80;
|
|
||||||
|
|
||||||
public static void inc(byte[] nonce) {
|
|
||||||
for (int i = nonce.length - 1; i >= 0; i--) {
|
|
||||||
nonce[i]++;
|
|
||||||
if (nonce[i] != 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases nonce by value, which is used as an unsigned byte value.
|
|
||||||
*
|
|
||||||
* @param nonce an unsigned number
|
|
||||||
* @param value to be added to nonce
|
|
||||||
*/
|
|
||||||
public static void inc(byte[] nonce, byte value) {
|
|
||||||
int i = nonce.length - 1;
|
|
||||||
nonce[i] += value;
|
|
||||||
if (value > 0 && (nonce[i] < 0 || nonce[i] >= value))
|
|
||||||
return;
|
|
||||||
if (value < 0 && (nonce[i] < 0 && nonce[i] >= value))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (i = i - 1; i >= 0; i--) {
|
|
||||||
nonce[i]++;
|
|
||||||
if (nonce[i] != 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if a < b.
|
|
||||||
*/
|
|
||||||
public static boolean lt(byte[] a, byte[] b) {
|
|
||||||
byte[] max = (a.length > b.length ? a : b);
|
|
||||||
byte[] min = (max == a ? b : a);
|
|
||||||
int diff = max.length - min.length;
|
|
||||||
|
|
||||||
for (int i = 0; i < max.length - min.length; i++) {
|
|
||||||
if (max[i] != 0) return a != max;
|
|
||||||
}
|
|
||||||
for (int i = diff; i < max.length; i++) {
|
|
||||||
if (max[i] != min[i - diff]) {
|
|
||||||
return lt(max[i], min[i - diff]) == (a == max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if a < b, where the first [size] bytes are used as the numbers to check.
|
|
||||||
*/
|
|
||||||
public static boolean lt(byte[] a, byte[] b, int size) {
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
if (a[i] != b[i]) {
|
|
||||||
return lt(a[i], b[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean lt(byte a, byte b) {
|
|
||||||
return (a ^ BYTE_0x80) < (b ^ BYTE_0x80);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a new byte array of length, left-padded with '0'.
|
|
||||||
*/
|
|
||||||
public static byte[] expand(byte[] source, int size) {
|
|
||||||
byte[] result = new byte[size];
|
|
||||||
System.arraycopy(source, 0, result, size - source.length, source.length);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a new byte array containing the first <em>size</em> bytes of the given array.
|
|
||||||
*/
|
|
||||||
public static byte[] truncate(byte[] source, int size) {
|
|
||||||
byte[] result = new byte[size];
|
|
||||||
System.arraycopy(source, 0, result, 0, size);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the byte array that hex represents. This is meant for test use and should be rewritten if used in
|
|
||||||
* production code.
|
|
||||||
*/
|
|
||||||
public static byte[] fromHex(String hex) {
|
|
||||||
if (hex.length() % 2 != 0) throw new IllegalArgumentException("expected even number of characters");
|
|
||||||
byte[] result = new byte[hex.length() / 2];
|
|
||||||
for (int i = 0; i < result.length; i++) {
|
|
||||||
result[i] = (byte) (hexValue(hex.charAt(i * 2)) * 16);
|
|
||||||
result[i] += hexValue(hex.charAt(i * 2 + 1));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int hexValue(char c) {
|
|
||||||
if (c >= '0' && c <= '9') {
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
if (c >= 'a' && c <= 'f') {
|
|
||||||
return 10 + c - 'a';
|
|
||||||
}
|
|
||||||
if (c >= 'A' && c <= 'F') {
|
|
||||||
return 10 + c - 'A';
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("'" + c + "' is not a valid hex value");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the number of leading '0' of a byte array.
|
|
||||||
*/
|
|
||||||
public static int numberOfLeadingZeros(byte[] bytes) {
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < bytes.length; i++) {
|
|
||||||
if (bytes[i] != 0) return i;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for a value within a callback method to be set.
|
|
||||||
*/
|
|
||||||
public class CallbackWaiter<T> {
|
|
||||||
private final long startTime = System.currentTimeMillis();
|
|
||||||
private volatile boolean isSet;
|
|
||||||
private T value;
|
|
||||||
private long time;
|
|
||||||
|
|
||||||
public void setValue(T value) {
|
|
||||||
synchronized (this) {
|
|
||||||
this.time = System.currentTimeMillis() - startTime;
|
|
||||||
this.value = value;
|
|
||||||
this.isSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T waitForValue() throws InterruptedException {
|
|
||||||
while (!isSet) {
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
synchronized (this) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,152 +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 in, AccessCounter counter) throws IOException {
|
|
||||||
int length = uint16(in, counter);
|
|
||||||
return bytes(in, length, counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] varBytes(InputStream in) throws IOException {
|
|
||||||
return varBytes(in, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] varBytes(InputStream in, AccessCounter counter) throws IOException {
|
|
||||||
int length = (int) varInt(in, counter);
|
|
||||||
return bytes(in, length, counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] bytes(InputStream in, int count) throws IOException {
|
|
||||||
return bytes(in, count, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] bytes(InputStream in, int count, AccessCounter counter) throws IOException {
|
|
||||||
byte[] result = new byte[count];
|
|
||||||
int off = 0;
|
|
||||||
while (off < count) {
|
|
||||||
int read = in.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 in) throws IOException {
|
|
||||||
int length = (int) varInt(in);
|
|
||||||
long[] result = new long[length];
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
result[i] = varInt(in);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long varInt(InputStream in) throws IOException {
|
|
||||||
return varInt(in, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long varInt(InputStream in, AccessCounter counter) throws IOException {
|
|
||||||
int first = in.read();
|
|
||||||
inc(counter);
|
|
||||||
switch (first) {
|
|
||||||
case 0xfd:
|
|
||||||
return uint16(in, counter);
|
|
||||||
case 0xfe:
|
|
||||||
return uint32(in, counter);
|
|
||||||
case 0xff:
|
|
||||||
return int64(in, counter);
|
|
||||||
default:
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint8(InputStream in) throws IOException {
|
|
||||||
return in.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint16(InputStream in) throws IOException {
|
|
||||||
return uint16(in, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int uint16(InputStream in, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 2);
|
|
||||||
return in.read() << 8 | in.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long uint32(InputStream in) throws IOException {
|
|
||||||
return uint32(in, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long uint32(InputStream in, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 4);
|
|
||||||
return in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long uint32(ByteBuffer in) {
|
|
||||||
return u(in.get()) << 24 | u(in.get()) << 16 | u(in.get()) << 8 | u(in.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int int32(InputStream in) throws IOException {
|
|
||||||
return int32(in, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int int32(InputStream in, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 4);
|
|
||||||
return ByteBuffer.wrap(bytes(in, 4)).getInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long int64(InputStream in) throws IOException {
|
|
||||||
return int64(in, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long int64(InputStream in, AccessCounter counter) throws IOException {
|
|
||||||
inc(counter, 8);
|
|
||||||
return ByteBuffer.wrap(bytes(in, 8)).getLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String varString(InputStream in) throws IOException {
|
|
||||||
return varString(in, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String varString(InputStream in, AccessCounter counter) throws IOException {
|
|
||||||
int length = (int) varInt(in, 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(in, length, counter), "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the given byte as if it were unsigned.
|
|
||||||
*/
|
|
||||||
private static int u(byte b) {
|
|
||||||
return b & 0xFF;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,207 +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 ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.nio.Buffer;
|
|
||||||
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 varIntList(long[] values, ByteBuffer buffer) {
|
|
||||||
varInt(values.length, buffer);
|
|
||||||
for (long value : values) {
|
|
||||||
varInt(value, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varInt(long value, OutputStream stream) throws IOException {
|
|
||||||
varInt(value, stream, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varInt(long value, ByteBuffer buffer) {
|
|
||||||
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 longs large enough for being
|
|
||||||
// recognized as negatives aren't realistic.
|
|
||||||
buffer.put((byte) 0xff);
|
|
||||||
buffer.putLong(value);
|
|
||||||
} else if (value < 0xfd) {
|
|
||||||
buffer.put((byte) value);
|
|
||||||
} else if (value <= 0xffffL) {
|
|
||||||
buffer.put((byte) 0xfd);
|
|
||||||
buffer.putShort((short) value);
|
|
||||||
} else if (value <= 0xffffffffL) {
|
|
||||||
buffer.put((byte) 0xfe);
|
|
||||||
buffer.putInt((int) value);
|
|
||||||
} else {
|
|
||||||
buffer.put((byte) 0xff);
|
|
||||||
buffer.putLong(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] varInt(long value) {
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(9);
|
|
||||||
varInt(value, buffer);
|
|
||||||
buffer.flip();
|
|
||||||
return Bytes.truncate(buffer.array(), buffer.limit());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException {
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(9);
|
|
||||||
varInt(value, buffer);
|
|
||||||
buffer.flip();
|
|
||||||
stream.write(buffer.array(), 0, buffer.limit());
|
|
||||||
inc(counter, buffer.limit());
|
|
||||||
}
|
|
||||||
|
|
||||||
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(2).putShort((short) value).array());
|
|
||||||
inc(counter, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void int16(long value, ByteBuffer buffer) {
|
|
||||||
buffer.putShort((short) value);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 int32(long value, ByteBuffer buffer) {
|
|
||||||
buffer.putInt((int) value);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 int64(long value, ByteBuffer buffer) {
|
|
||||||
buffer.putLong(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 varString(String value, ByteBuffer buffer) {
|
|
||||||
try {
|
|
||||||
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()
|
|
||||||
buffer.put(varInt(bytes.length));
|
|
||||||
buffer.put(bytes);
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varBytes(byte[] data, OutputStream out) throws IOException {
|
|
||||||
varInt(data.length, out);
|
|
||||||
out.write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void varBytes(byte[] data, ByteBuffer buffer) {
|
|
||||||
varInt(data.length, buffer);
|
|
||||||
buffer.put(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.
|
|
||||||
*/
|
|
||||||
public static byte[] bytes(Streamable streamable) {
|
|
||||||
if (streamable == null) return null;
|
|
||||||
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
try {
|
|
||||||
streamable.write(stream);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
public static byte[] bytes(Streamable streamable, int padding) {
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
try {
|
|
||||||
streamable.write(stream);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
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,32 +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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by chris on 20.07.15.
|
|
||||||
*/
|
|
||||||
public class Points {
|
|
||||||
public static byte[] getX(byte[] P) {
|
|
||||||
return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] getY(byte[] P) {
|
|
||||||
return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length);
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user