Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8431f984a6 | |||
| 207f761196 | |||
| 3d6ff5993e | |||
| d10412346d | |||
| 9b7c456435 | |||
| d12ffcfdf8 | |||
| 277b1bdca7 | |||
| 6cd50e054e | |||
| a44a562a07 | |||
| 712151b0ea |
@@ -1,7 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
+3
-162
@@ -1,162 +1,3 @@
|
||||
# Created by https://www.gitignore.io
|
||||
|
||||
*.log
|
||||
|
||||
### 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
|
||||
|
||||
_site
|
||||
.sass-cache
|
||||
.jekyll-metadata
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
language: java
|
||||
sudo: false # faster builds
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
before_install:
|
||||
- pip install --user codecov
|
||||
|
||||
after_success:
|
||||
- codecov
|
||||
-1308
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.
|
||||
@@ -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.
|
||||
@@ -1,137 +0,0 @@
|
||||
Jabit
|
||||
=====
|
||||
[](https://maven-badges.herokuapp.com/maven-central/ch.dissem.jabit/jabit-core)
|
||||
[](http://www.javadoc.io/doc/ch.dissem.jabit/jabit-core)
|
||||
[](https://raw.githubusercontent.com/Dissem/Jabit/master/LICENSE)
|
||||
[](https://kiwiirc.com/client/irc.freenode.net/#jabit)
|
||||
|
||||
A Java implementation for the Bitmessage protocol. To build, use command `./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
|
||||
[](https://travis-ci.org/Dissem/Jabit)
|
||||
[](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144281)
|
||||
[](https://codecov.io/github/Dissem/Jabit?branch=master)
|
||||
|
||||
#### Develop
|
||||
[](https://travis-ci.org/Dissem/Jabit?branch=develop)
|
||||
[](https://www.codacy.com/app/chrigu-meyer/Jabit/dashboard?bid=3144279)
|
||||
[](https://codecov.io/github/Dissem/Jabit?branch=develop)
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
Please be aware that Version 2.0.0 has some breaking changes, most notably in the repository implementations -- please take special care when upgrading them. If you don't implement your own repositories, you should be able to quickly find and fix any compilation errors caused by the few other breaking changes.
|
||||
|
||||
There is also a new network handler which comes highly recommended. If you're having any network problems, please make sure you use `NioNetworkHandler` instead of the now deprecated `DefaultNetworkHandler`.
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
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
@@ -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
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
-157
@@ -1,157 +0,0 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.4-3'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.15.0'
|
||||
id "io.spring.dependency-management" version "1.0.3.RELEASE"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'signing'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'gitflow-version'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
|
||||
sourceCompatibility = 1.7
|
||||
targetCompatibility = 1.7
|
||||
group = 'ch.dissem.jabit'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect"
|
||||
}
|
||||
|
||||
test {
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
}
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.6"
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.6"
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar, sourcesJar
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Implementation-Title': "Jabit ${project.name.capitalize()}",
|
||||
'Implementation-Version': version
|
||||
}
|
||||
baseName "jabit-${project.name}"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
dependencyManagement {
|
||||
dependencies {
|
||||
dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
|
||||
entry 'kotlin-stdlib-jre7'
|
||||
entry 'kotlin-reflect'
|
||||
}
|
||||
dependencySet(group: 'org.slf4j', version: '1.7.25') {
|
||||
entry 'slf4j-api'
|
||||
entry 'slf4j-simple'
|
||||
}
|
||||
|
||||
dependency 'ch.dissem.msgpack:msgpack:2.0.0'
|
||||
dependency 'org.bouncycastle:bcprov-jdk15on:1.57'
|
||||
dependency 'com.madgag.spongycastle:prov:1.56.0.0'
|
||||
dependency 'org.apache.commons:commons-lang3:3.6'
|
||||
dependency 'org.flywaydb:flyway-core:4.2.0'
|
||||
dependency 'com.beust:klaxon:0.31'
|
||||
|
||||
dependency 'args4j:args4j:2.33'
|
||||
dependency 'org.ini4j:ini4j:0.5.4'
|
||||
dependency 'com.h2database:h2:1.4.196'
|
||||
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'org.hamcrest:hamcrest-library:1.3'
|
||||
dependency 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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') {
|
||||
doLast { println "Version deduced from git: '${project.version}'" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
implementation-class=ch.dissem.gradle.GitFlowVersion
|
||||
@@ -1,51 +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'
|
||||
compile 'ch.dissem.msgpack:msgpack:1.0.0'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||
testCompile project(':cryptography-bc')
|
||||
}
|
||||
|
||||
def generatedResources = "${project.buildDir}/generated-resources/main"
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
output.dir(generatedResources, builtBy: 'generateVersionInfo')
|
||||
}
|
||||
}
|
||||
task('generateVersionInfo') {
|
||||
doLast {
|
||||
def dir = new File(generatedResources)
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs()
|
||||
}
|
||||
def file = new File(generatedResources, "version")
|
||||
file.write(project.version.toString())
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.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.
|
||||
* <p>
|
||||
* Note: This class can't yet be ported to Kotlin, as with Kotlin byte + byte = int, which
|
||||
* would be rather inefficient in our case.
|
||||
* </p>
|
||||
*/
|
||||
public class Bytes {
|
||||
public 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,500 +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.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.CustomMessage
|
||||
import ch.dissem.bitmessage.entity.MessagePayload
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Status.DRAFT
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||
import ch.dissem.bitmessage.entity.payload.Broadcast
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.ports.*
|
||||
import ch.dissem.bitmessage.utils.Property
|
||||
import ch.dissem.bitmessage.utils.UnixTime.HOUR
|
||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
*
|
||||
* Use this class if you want to create a Bitmessage client.
|
||||
* You'll need the Builder to create a BitmessageContext, and set the following properties:
|
||||
*
|
||||
* * addressRepo
|
||||
* * inventory
|
||||
* * nodeRegistry
|
||||
* * networkHandler
|
||||
* * messageRepo
|
||||
* * streams
|
||||
*
|
||||
*
|
||||
* The default implementations in the different module builds can be used.
|
||||
*
|
||||
* The port defaults to 8444 (the default Bitmessage port)
|
||||
*/
|
||||
class BitmessageContext(
|
||||
cryptography: Cryptography,
|
||||
inventory: Inventory,
|
||||
nodeRegistry: NodeRegistry,
|
||||
networkHandler: NetworkHandler,
|
||||
addressRepository: AddressRepository,
|
||||
messageRepository: MessageRepository,
|
||||
proofOfWorkRepository: ProofOfWorkRepository,
|
||||
proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(),
|
||||
customCommandHandler: CustomCommandHandler = object : CustomCommandHandler {
|
||||
override fun handle(request: CustomMessage): MessagePayload? {
|
||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||
return null
|
||||
}
|
||||
},
|
||||
listener: Listener,
|
||||
labeler: Labeler = DefaultLabeler(),
|
||||
userAgent: String? = null,
|
||||
port: Int = 8444,
|
||||
connectionTTL: Long = 30 * MINUTE,
|
||||
connectionLimit: Int = 150,
|
||||
sendPubkeyOnIdentityCreation: Boolean = true,
|
||||
doMissingProofOfWorkDelayInSeconds: Int = 30
|
||||
) {
|
||||
|
||||
private constructor(builder: BitmessageContext.Builder) : this(
|
||||
builder.cryptography,
|
||||
builder.inventory,
|
||||
builder.nodeRegistry,
|
||||
builder.networkHandler,
|
||||
builder.addressRepo,
|
||||
builder.messageRepo,
|
||||
builder.proofOfWorkRepository,
|
||||
builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(),
|
||||
builder.customCommandHandler ?: object : CustomCommandHandler {
|
||||
override fun handle(request: CustomMessage): MessagePayload? {
|
||||
BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.")
|
||||
return null
|
||||
}
|
||||
},
|
||||
builder.listener,
|
||||
builder.labeler ?: DefaultLabeler(),
|
||||
builder.userAgent,
|
||||
builder.port,
|
||||
builder.connectionTTL,
|
||||
builder.connectionLimit,
|
||||
builder.sendPubkeyOnIdentityCreation,
|
||||
builder.doMissingProofOfWorkDelay
|
||||
)
|
||||
|
||||
private val sendPubkeyOnIdentityCreation: Boolean
|
||||
|
||||
/**
|
||||
* The [InternalContext] - normally you wouldn't need it,
|
||||
* unless you are doing something crazy with the protocol.
|
||||
*/
|
||||
val internals: InternalContext
|
||||
@JvmName("internals") get
|
||||
|
||||
val labeler: Labeler
|
||||
@JvmName("labeler") get
|
||||
|
||||
val addresses: AddressRepository
|
||||
@JvmName("addresses") get
|
||||
|
||||
val messages: MessageRepository
|
||||
@JvmName("messages") get
|
||||
|
||||
fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress {
|
||||
val identity = BitmessageAddress(PrivateKey(
|
||||
shorter,
|
||||
internals.streams[0],
|
||||
NETWORK_NONCE_TRIALS_PER_BYTE,
|
||||
NETWORK_EXTRA_BYTES,
|
||||
*features
|
||||
))
|
||||
internals.addressRepository.save(identity)
|
||||
if (sendPubkeyOnIdentityCreation) {
|
||||
internals.sendPubkey(identity, identity.stream)
|
||||
}
|
||||
return identity
|
||||
}
|
||||
|
||||
fun joinChan(passphrase: String, address: String): BitmessageAddress {
|
||||
val chan = BitmessageAddress.chan(address, passphrase)
|
||||
chan.alias = passphrase
|
||||
internals.addressRepository.save(chan)
|
||||
return chan
|
||||
}
|
||||
|
||||
fun createChan(passphrase: String): BitmessageAddress {
|
||||
// FIXME: hardcoded stream number
|
||||
val chan = BitmessageAddress.chan(1, passphrase)
|
||||
internals.addressRepository.save(chan)
|
||||
return chan
|
||||
}
|
||||
|
||||
fun createDeterministicAddresses(
|
||||
passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
|
||||
val result = BitmessageAddress.deterministic(
|
||||
passphrase, numberOfAddresses, version, stream, shorter)
|
||||
for (i in result.indices) {
|
||||
val address = result[i]
|
||||
address.alias = "deterministic (" + (i + 1) + ")"
|
||||
internals.addressRepository.save(address)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun broadcast(from: BitmessageAddress, subject: String, message: String) {
|
||||
send(Plaintext(
|
||||
type = BROADCAST,
|
||||
from = from,
|
||||
subject = subject,
|
||||
body = message,
|
||||
status = DRAFT
|
||||
))
|
||||
}
|
||||
|
||||
fun send(from: BitmessageAddress, to: BitmessageAddress, subject: String, message: String) {
|
||||
if (from.privateKey == null) {
|
||||
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||
}
|
||||
send(Plaintext(
|
||||
type = MSG,
|
||||
from = from,
|
||||
to = to,
|
||||
subject = subject,
|
||||
body = message
|
||||
))
|
||||
}
|
||||
|
||||
fun send(msg: Plaintext) {
|
||||
if (msg.from.privateKey == null) {
|
||||
throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.")
|
||||
}
|
||||
labeler.markAsSending(msg)
|
||||
val to = msg.to
|
||||
if (to != null) {
|
||||
if (to.pubkey == null) {
|
||||
LOG.info("Public key is missing from recipient. Requesting.")
|
||||
internals.requestPubkey(to)
|
||||
}
|
||||
if (to.pubkey == null) {
|
||||
internals.messageRepository.save(msg)
|
||||
}
|
||||
}
|
||||
if (to == null || to.pubkey != null) {
|
||||
LOG.info("Sending message.")
|
||||
internals.messageRepository.save(msg)
|
||||
if (msg.type == MSG) {
|
||||
internals.send(msg)
|
||||
} else {
|
||||
internals.send(
|
||||
msg.from,
|
||||
to,
|
||||
Factory.getBroadcast(msg),
|
||||
msg.ttl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startup() {
|
||||
internals.networkHandler.start()
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
internals.networkHandler.stop()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host a trusted node that must be reliable (it's used for every synchronization)
|
||||
* @param port of the trusted host, default is 8444
|
||||
* @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed,
|
||||
* even if not all objects were fetched
|
||||
* @param wait waits for the synchronization thread to finish
|
||||
*/
|
||||
fun synchronize(host: InetAddress, port: Int, timeoutInSeconds: Long, wait: Boolean) {
|
||||
val future = internals.networkHandler.synchronize(host, port, timeoutInSeconds)
|
||||
if (wait) {
|
||||
try {
|
||||
future.get()
|
||||
} catch (e: InterruptedException) {
|
||||
LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.")
|
||||
future.cancel(true)
|
||||
} catch (e: CancellationException) {
|
||||
LOG.debug(e.message, e)
|
||||
} catch (e: ExecutionException) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
||||
* the response, which in turn is expected to be a [CustomMessage].
|
||||
*
|
||||
* @param server the node's address
|
||||
* @param port the node's port
|
||||
* @param request the request
|
||||
* @return the response
|
||||
*/
|
||||
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage {
|
||||
return internals.networkHandler.send(server, port, request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes expired objects from the inventory. You should call this method regularly,
|
||||
* e.g. daily and on each shutdown.
|
||||
*/
|
||||
fun cleanup() {
|
||||
internals.inventory.cleanup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends messages again whose time to live expired without being acknowledged. (And whose
|
||||
* recipient is expected to send acknowledgements.
|
||||
*
|
||||
* You should call this method regularly, but be aware of the following:
|
||||
*
|
||||
* * As messages might be sent, POW will be done. It is therefore not advised to
|
||||
* call it on shutdown.
|
||||
* * It shouldn't be called right after startup, as it's possible the missing
|
||||
* acknowledgement was sent while the client was offline.
|
||||
* * Other than that, the call isn't expensive as long as there is no message
|
||||
* to send, so it might be a good idea to just call it every few minutes.
|
||||
*/
|
||||
fun resendUnacknowledgedMessages() {
|
||||
internals.resendUnacknowledged()
|
||||
}
|
||||
|
||||
fun isRunning() = internals.networkHandler.isRunning
|
||||
|
||||
fun addContact(contact: BitmessageAddress) {
|
||||
internals.addressRepository.save(contact)
|
||||
if (contact.pubkey == null) {
|
||||
// If it already existed, the saved contact might have the public key
|
||||
if (internals.addressRepository.getAddress(contact.address)!!.pubkey == null) {
|
||||
internals.requestPubkey(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addSubscribtion(address: BitmessageAddress) {
|
||||
address.isSubscribed = true
|
||||
internals.addressRepository.save(address)
|
||||
tryToFindBroadcastsForAddress(address)
|
||||
}
|
||||
|
||||
private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) {
|
||||
for (objectMessage in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) {
|
||||
try {
|
||||
val broadcast = objectMessage.payload as Broadcast
|
||||
broadcast.decrypt(address)
|
||||
// This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with
|
||||
// other subscriptions and the interface stays as simple as possible.
|
||||
internals.networkListener.receive(objectMessage)
|
||||
} catch (ignore: DecryptionFailedException) {
|
||||
} catch (e: Exception) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun status(): Property {
|
||||
return Property("status",
|
||||
Property("user agent", internals.userAgent),
|
||||
internals.networkHandler.getNetworkStatus(),
|
||||
Property("unacknowledged", internals.messageRepository.findMessagesToResend().size)
|
||||
)
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun receive(plaintext: Plaintext)
|
||||
|
||||
/**
|
||||
* A message listener that needs a [BitmessageContext], i.e. for implementing some sort of chat bot.
|
||||
*/
|
||||
interface WithContext : Listener {
|
||||
fun setContext(ctx: BitmessageContext)
|
||||
}
|
||||
}
|
||||
|
||||
class Builder {
|
||||
internal var port = 8444
|
||||
internal var inventory by Delegates.notNull<Inventory>()
|
||||
internal var nodeRegistry by Delegates.notNull<NodeRegistry>()
|
||||
internal var networkHandler by Delegates.notNull<NetworkHandler>()
|
||||
internal var addressRepo by Delegates.notNull<AddressRepository>()
|
||||
internal var messageRepo by Delegates.notNull<MessageRepository>()
|
||||
internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>()
|
||||
internal var proofOfWorkEngine: ProofOfWorkEngine? = null
|
||||
internal var cryptography by Delegates.notNull<Cryptography>()
|
||||
internal var customCommandHandler: CustomCommandHandler? = null
|
||||
internal var labeler: Labeler? = null
|
||||
internal var userAgent: String? = null
|
||||
internal var listener by Delegates.notNull<Listener>()
|
||||
internal var connectionLimit = 150
|
||||
internal var connectionTTL = 30 * MINUTE
|
||||
internal var sendPubkeyOnIdentityCreation = true
|
||||
internal var doMissingProofOfWorkDelay = 30
|
||||
|
||||
fun port(port: Int): Builder {
|
||||
this.port = port
|
||||
return this
|
||||
}
|
||||
|
||||
fun inventory(inventory: Inventory): Builder {
|
||||
this.inventory = inventory
|
||||
return this
|
||||
}
|
||||
|
||||
fun nodeRegistry(nodeRegistry: NodeRegistry): Builder {
|
||||
this.nodeRegistry = nodeRegistry
|
||||
return this
|
||||
}
|
||||
|
||||
fun networkHandler(networkHandler: NetworkHandler): Builder {
|
||||
this.networkHandler = networkHandler
|
||||
return this
|
||||
}
|
||||
|
||||
fun addressRepo(addressRepo: AddressRepository): Builder {
|
||||
this.addressRepo = addressRepo
|
||||
return this
|
||||
}
|
||||
|
||||
fun messageRepo(messageRepo: MessageRepository): Builder {
|
||||
this.messageRepo = messageRepo
|
||||
return this
|
||||
}
|
||||
|
||||
fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder {
|
||||
this.proofOfWorkRepository = proofOfWorkRepository
|
||||
return this
|
||||
}
|
||||
|
||||
fun cryptography(cryptography: Cryptography): Builder {
|
||||
this.cryptography = cryptography
|
||||
return this
|
||||
}
|
||||
|
||||
fun customCommandHandler(handler: CustomCommandHandler): Builder {
|
||||
this.customCommandHandler = handler
|
||||
return this
|
||||
}
|
||||
|
||||
fun proofOfWorkEngine(proofOfWorkEngine: ProofOfWorkEngine): Builder {
|
||||
this.proofOfWorkEngine = proofOfWorkEngine
|
||||
return this
|
||||
}
|
||||
|
||||
fun labeler(labeler: Labeler): Builder {
|
||||
this.labeler = labeler
|
||||
return this
|
||||
}
|
||||
|
||||
fun listener(listener: Listener): Builder {
|
||||
this.listener = listener
|
||||
return this
|
||||
}
|
||||
|
||||
@JvmName("kotlinListener")
|
||||
fun listener(listener: (Plaintext) -> Unit): Builder {
|
||||
this.listener = object : Listener {
|
||||
override fun receive(plaintext: Plaintext) {
|
||||
listener.invoke(plaintext)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun connectionLimit(connectionLimit: Int): Builder {
|
||||
this.connectionLimit = connectionLimit
|
||||
return this
|
||||
}
|
||||
|
||||
fun connectionTTL(hours: Int): Builder {
|
||||
this.connectionTTL = hours * HOUR
|
||||
return this
|
||||
}
|
||||
|
||||
fun doMissingProofOfWorkDelay(seconds: Int) {
|
||||
this.doMissingProofOfWorkDelay = seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* By default a client will send the public key when an identity is being created. On weaker devices
|
||||
* this behaviour might not be desirable.
|
||||
*/
|
||||
fun doNotSendPubkeyOnIdentityCreation(): Builder {
|
||||
this.sendPubkeyOnIdentityCreation = false
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): BitmessageContext {
|
||||
return BitmessageContext(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
this.labeler = labeler
|
||||
this.internals = InternalContext(
|
||||
cryptography,
|
||||
inventory,
|
||||
nodeRegistry,
|
||||
networkHandler,
|
||||
addressRepository,
|
||||
messageRepository,
|
||||
proofOfWorkRepository,
|
||||
proofOfWorkEngine,
|
||||
customCommandHandler,
|
||||
listener,
|
||||
labeler,
|
||||
userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/",
|
||||
port,
|
||||
connectionTTL,
|
||||
connectionLimit
|
||||
)
|
||||
this.addresses = addressRepository
|
||||
this.messages = messageRepository
|
||||
this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation
|
||||
(listener as? Listener.WithContext)?.setContext(this)
|
||||
internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val CURRENT_VERSION = 3
|
||||
private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java)
|
||||
|
||||
val version: String by lazy {
|
||||
BitmessageContext::class.java.getResource("/version")?.readText() ?: "local build"
|
||||
}
|
||||
@JvmStatic get
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED
|
||||
import ch.dissem.bitmessage.entity.payload.*
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.ports.AlreadyStoredException
|
||||
import ch.dissem.bitmessage.ports.Labeler
|
||||
import ch.dissem.bitmessage.ports.NetworkHandler
|
||||
import ch.dissem.bitmessage.utils.Strings.hex
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
|
||||
open class DefaultMessageListener(
|
||||
private val labeler: Labeler,
|
||||
private val listener: BitmessageContext.Listener
|
||||
) : NetworkHandler.MessageListener, InternalContext.ContextHolder {
|
||||
private lateinit var ctx: InternalContext
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
override fun receive(objectMessage: ObjectMessage) {
|
||||
val payload = objectMessage.payload
|
||||
|
||||
when (payload.type) {
|
||||
ObjectType.GET_PUBKEY -> {
|
||||
receive(objectMessage, payload as GetPubkey)
|
||||
}
|
||||
ObjectType.PUBKEY -> {
|
||||
receive(payload as Pubkey)
|
||||
}
|
||||
ObjectType.MSG -> {
|
||||
receive(objectMessage, payload as Msg)
|
||||
}
|
||||
ObjectType.BROADCAST -> {
|
||||
receive(objectMessage, payload as Broadcast)
|
||||
}
|
||||
null -> {
|
||||
if (payload is GenericPayload) {
|
||||
receive(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(objectMessage: ObjectMessage, getPubkey: GetPubkey) {
|
||||
val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag)
|
||||
if (identity?.privateKey != null && !identity.isChan) {
|
||||
LOG.info("Got pubkey request for identity " + identity)
|
||||
// FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days
|
||||
ctx.sendPubkey(identity, objectMessage.stream)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(pubkey: Pubkey) {
|
||||
try {
|
||||
if (pubkey is V4Pubkey) {
|
||||
ctx.addressRepository.findContact(pubkey.tag)?.let {
|
||||
if (it.pubkey == null) {
|
||||
pubkey.decrypt(it.publicDecryptionKey)
|
||||
updatePubkey(it, pubkey)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.addressRepository.findContact(pubkey.ripe)?.let {
|
||||
if (it.pubkey == null) {
|
||||
updatePubkey(it, pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_: DecryptionFailedException) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePubkey(address: BitmessageAddress, pubkey: Pubkey) {
|
||||
address.pubkey = pubkey
|
||||
LOG.info("Got pubkey for contact " + address)
|
||||
ctx.addressRepository.save(address)
|
||||
val messages = ctx.messageRepository.findMessages(PUBKEY_REQUESTED, address)
|
||||
LOG.info("Sending " + messages.size + " messages for contact " + address)
|
||||
for (msg in messages) {
|
||||
ctx.labeler.markAsSending(msg)
|
||||
ctx.messageRepository.save(msg)
|
||||
ctx.send(msg)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(objectMessage: ObjectMessage, msg: Msg) {
|
||||
for (identity in ctx.addressRepository.getIdentities()) {
|
||||
try {
|
||||
msg.decrypt(identity.privateKey!!.privateEncryptionKey)
|
||||
val plaintext = msg.plaintext!!
|
||||
plaintext.to = identity
|
||||
if (!objectMessage.isSignatureValid(plaintext.from.pubkey!!)) {
|
||||
LOG.warn("Msg with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
|
||||
} else {
|
||||
receive(objectMessage.inventoryVector, plaintext)
|
||||
}
|
||||
break
|
||||
} catch (_: DecryptionFailedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(ack: GenericPayload) {
|
||||
if (ack.data.size == Msg.ACK_LENGTH) {
|
||||
ctx.messageRepository.getMessageForAck(ack.data)?.let {
|
||||
ctx.labeler.markAsAcknowledged(it)
|
||||
ctx.messageRepository.save(it)
|
||||
} ?: LOG.debug("Message not found for ack ${hex(ack.data)}")
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(objectMessage: ObjectMessage, broadcast: Broadcast) {
|
||||
val tag = (broadcast as? V5Broadcast)?.tag
|
||||
ctx.addressRepository.getSubscriptions(broadcast.version)
|
||||
.filter { tag == null || Arrays.equals(tag, it.tag) }
|
||||
.forEach {
|
||||
try {
|
||||
broadcast.decrypt(it.publicDecryptionKey)
|
||||
if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) {
|
||||
LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
|
||||
} else {
|
||||
receive(objectMessage.inventoryVector, broadcast.plaintext!!)
|
||||
}
|
||||
} catch (_: DecryptionFailedException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun receive(iv: InventoryVector, msg: Plaintext) {
|
||||
val contact = ctx.addressRepository.getAddress(msg.from.address)
|
||||
if (contact != null && contact.pubkey == null) {
|
||||
updatePubkey(contact, msg.from.pubkey!!)
|
||||
}
|
||||
|
||||
msg.inventoryVector = iv
|
||||
try {
|
||||
ctx.messageRepository.save(msg)
|
||||
// We might need the ID here, so we need to add the labels and save it again
|
||||
labeler.setLabels(msg)
|
||||
ctx.messageRepository.save(msg)
|
||||
listener.receive(msg)
|
||||
|
||||
if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) {
|
||||
msg.ackMessage?.let {
|
||||
ctx.inventory.storeObject(it)
|
||||
ctx.networkHandler.offer(it.inventoryVector)
|
||||
} ?: LOG.debug("ack message expected")
|
||||
}
|
||||
} catch (e: AlreadyStoredException) {
|
||||
LOG.trace("Message was already received before.", e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(DefaultMessageListener::class.java)
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Encrypted
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.payload.*
|
||||
import ch.dissem.bitmessage.ports.*
|
||||
import ch.dissem.bitmessage.utils.Singleton
|
||||
import ch.dissem.bitmessage.utils.TTL
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* The internal context should normally only be used for port implementations. If you need it in your client
|
||||
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
|
||||
* get extended.
|
||||
*
|
||||
*
|
||||
* On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply.
|
||||
*
|
||||
*/
|
||||
class InternalContext(
|
||||
val cryptography: Cryptography,
|
||||
val inventory: ch.dissem.bitmessage.ports.Inventory,
|
||||
val nodeRegistry: NodeRegistry,
|
||||
val networkHandler: NetworkHandler,
|
||||
val addressRepository: AddressRepository,
|
||||
val messageRepository: ch.dissem.bitmessage.ports.MessageRepository,
|
||||
val proofOfWorkRepository: ProofOfWorkRepository,
|
||||
val proofOfWorkEngine: ProofOfWorkEngine,
|
||||
val customCommandHandler: CustomCommandHandler,
|
||||
listener: BitmessageContext.Listener,
|
||||
val labeler: Labeler,
|
||||
|
||||
val userAgent: String,
|
||||
|
||||
val port: Int,
|
||||
val connectionTTL: Long,
|
||||
val connectionLimit: Int
|
||||
) {
|
||||
|
||||
private val threadPool = Executors.newCachedThreadPool()
|
||||
|
||||
val proofOfWorkService: ProofOfWorkService = ProofOfWorkService()
|
||||
val networkListener: NetworkHandler.MessageListener = DefaultMessageListener(labeler, listener)
|
||||
val clientNonce: Long = cryptography.randomNonce()
|
||||
private val _streams = TreeSet<Long>()
|
||||
val streams: LongArray
|
||||
get() = _streams.toLongArray()
|
||||
|
||||
init {
|
||||
Singleton.initialize(cryptography)
|
||||
|
||||
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
|
||||
addressRepository.getIdentities().mapTo(_streams) { it.stream }
|
||||
addressRepository.getSubscriptions().mapTo(_streams) { it.stream }
|
||||
if (_streams.isEmpty()) {
|
||||
_streams.add(1L)
|
||||
}
|
||||
|
||||
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
|
||||
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, labeler,
|
||||
networkListener)
|
||||
}
|
||||
|
||||
private fun init(vararg objects: Any) {
|
||||
objects.filter { it is ContextHolder }.forEach { (it as ContextHolder).setContext(this) }
|
||||
}
|
||||
|
||||
fun send(plaintext: Plaintext) {
|
||||
if (plaintext.ackMessage != null) {
|
||||
val expires = UnixTime.now + plaintext.ttl
|
||||
LOG.info("Expires at " + expires)
|
||||
proofOfWorkService.doProofOfWorkWithAck(plaintext, expires)
|
||||
} else {
|
||||
send(plaintext.from, plaintext.to, Msg(plaintext), plaintext.ttl)
|
||||
}
|
||||
}
|
||||
|
||||
fun send(from: BitmessageAddress, to: BitmessageAddress?, payload: ObjectPayload,
|
||||
timeToLive: Long) {
|
||||
val recipient = to ?: from
|
||||
val expires = UnixTime.now + timeToLive
|
||||
LOG.info("Expires at " + expires)
|
||||
val objectMessage = ObjectMessage(
|
||||
stream = recipient.stream,
|
||||
expiresTime = expires,
|
||||
payload = payload
|
||||
)
|
||||
if (objectMessage.isSigned) {
|
||||
objectMessage.sign(
|
||||
from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity")
|
||||
)
|
||||
}
|
||||
if (payload is Broadcast) {
|
||||
payload.encrypt()
|
||||
} else if (payload is Encrypted) {
|
||||
objectMessage.encrypt(
|
||||
recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available")
|
||||
)
|
||||
}
|
||||
proofOfWorkService.doProofOfWork(to, objectMessage)
|
||||
}
|
||||
|
||||
fun sendPubkey(identity: BitmessageAddress, targetStream: Long) {
|
||||
val expires = UnixTime.now + TTL.pubkey
|
||||
LOG.info("Expires at " + expires)
|
||||
val payload = identity.pubkey ?: throw IllegalArgumentException("The given address is no identity")
|
||||
val response = ObjectMessage(
|
||||
expiresTime = expires,
|
||||
stream = targetStream,
|
||||
payload = payload
|
||||
)
|
||||
response.sign(
|
||||
identity.privateKey ?: throw IllegalArgumentException("The given address is no identity")
|
||||
)
|
||||
response.encrypt(cryptography.createPublicKey(identity.publicDecryptionKey))
|
||||
// TODO: remember that the pubkey is just about to be sent, and on which stream!
|
||||
proofOfWorkService.doProofOfWork(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback
|
||||
* for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB.
|
||||
*/
|
||||
fun requestPubkey(contact: BitmessageAddress) {
|
||||
threadPool.execute {
|
||||
val stored = addressRepository.getAddress(contact.address)
|
||||
|
||||
tryToFindMatchingPubkey(contact)
|
||||
if (contact.pubkey != null) {
|
||||
if (stored != null) {
|
||||
stored.pubkey = contact.pubkey
|
||||
addressRepository.save(stored)
|
||||
} else {
|
||||
addressRepository.save(contact)
|
||||
}
|
||||
return@execute
|
||||
}
|
||||
|
||||
if (stored == null) {
|
||||
addressRepository.save(contact)
|
||||
}
|
||||
|
||||
val expires = UnixTime.now + TTL.getpubkey
|
||||
LOG.info("Expires at $expires")
|
||||
val request = ObjectMessage(
|
||||
stream = contact.stream,
|
||||
expiresTime = expires,
|
||||
payload = GetPubkey(contact)
|
||||
)
|
||||
proofOfWorkService.doProofOfWork(request)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryToFindMatchingPubkey(address: BitmessageAddress) {
|
||||
addressRepository.getAddress(address.address)?.let {
|
||||
address.alias = it.alias
|
||||
address.isSubscribed = it.isSubscribed
|
||||
}
|
||||
for (objectMessage in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) {
|
||||
try {
|
||||
val pubkey = objectMessage.payload as Pubkey
|
||||
if (address.version == 4L) {
|
||||
val v4Pubkey = pubkey as V4Pubkey
|
||||
if (Arrays.equals(address.tag, v4Pubkey.tag)) {
|
||||
v4Pubkey.decrypt(address.publicDecryptionKey)
|
||||
if (objectMessage.isSignatureValid(v4Pubkey)) {
|
||||
address.pubkey = v4Pubkey
|
||||
addressRepository.save(address)
|
||||
break
|
||||
} else {
|
||||
LOG.info("Found pubkey for $address but signature is invalid")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (Arrays.equals(pubkey.ripe, address.ripe)) {
|
||||
address.pubkey = pubkey
|
||||
addressRepository.save(address)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOG.debug(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resendUnacknowledged() {
|
||||
val messages = messageRepository.findMessagesToResend()
|
||||
for (message in messages) {
|
||||
send(message)
|
||||
messageRepository.save(message)
|
||||
}
|
||||
}
|
||||
|
||||
interface ContextHolder {
|
||||
fun setContext(context: InternalContext)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
|
||||
|
||||
@JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
|
||||
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.entity.*
|
||||
import ch.dissem.bitmessage.entity.payload.Msg
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
class ProofOfWorkService : ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
|
||||
|
||||
private lateinit var ctx: InternalContext
|
||||
private val cryptography by lazy { ctx.cryptography }
|
||||
private val powRepo by lazy { ctx.proofOfWorkRepository }
|
||||
private val messageRepo by lazy { ctx.messageRepository }
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
fun doMissingProofOfWork(delayInMilliseconds: Long) {
|
||||
val items = powRepo.getItems()
|
||||
if (items.isEmpty()) return
|
||||
|
||||
// Wait for 30 seconds, to let the application start up before putting heavy load on the CPU
|
||||
Timer().schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
LOG.info("Doing POW for " + items.size + " tasks.")
|
||||
for (initialHash in items) {
|
||||
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
||||
cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes,
|
||||
this@ProofOfWorkService)
|
||||
}
|
||||
}
|
||||
}, delayInMilliseconds)
|
||||
}
|
||||
|
||||
fun doProofOfWork(objectMessage: ObjectMessage) {
|
||||
doProofOfWork(null, objectMessage)
|
||||
}
|
||||
|
||||
fun doProofOfWork(recipient: BitmessageAddress?, objectMessage: ObjectMessage) {
|
||||
val pubkey = recipient?.pubkey
|
||||
|
||||
val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES
|
||||
|
||||
powRepo.putObject(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||
if (objectMessage.payload is PlaintextHolder) {
|
||||
objectMessage.payload.plaintext?.let {
|
||||
it.initialHash = cryptography.getInitialHash(objectMessage)
|
||||
messageRepo.save(it)
|
||||
} ?: LOG.error("PlaintextHolder without Plaintext shouldn't make it to the POW")
|
||||
}
|
||||
cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, this)
|
||||
}
|
||||
|
||||
fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) {
|
||||
val ack = plaintext.ackMessage!!
|
||||
messageRepo.save(plaintext)
|
||||
val item = Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES,
|
||||
expirationTime, plaintext)
|
||||
powRepo.putObject(item)
|
||||
cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this)
|
||||
}
|
||||
|
||||
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
|
||||
val (objectMessage, _, _, expirationTime, message) = powRepo.getItem(initialHash)
|
||||
if (message == null) {
|
||||
objectMessage.nonce = nonce
|
||||
messageRepo.getMessage(initialHash)?.let {
|
||||
it.inventoryVector = objectMessage.inventoryVector
|
||||
it.updateNextTry()
|
||||
ctx.labeler.markAsSent(it)
|
||||
messageRepo.save(it)
|
||||
}
|
||||
ctx.inventory.storeObject(objectMessage)
|
||||
ctx.networkHandler.offer(objectMessage.inventoryVector)
|
||||
} else {
|
||||
message.ackMessage!!.nonce = nonce
|
||||
val newObjectMessage = ObjectMessage.Builder()
|
||||
.stream(message.stream)
|
||||
.expiresTime(expirationTime!!)
|
||||
.payload(Msg(message))
|
||||
.build()
|
||||
if (newObjectMessage.isSigned) {
|
||||
newObjectMessage.sign(message.from.privateKey!!)
|
||||
}
|
||||
if (newObjectMessage.payload is Encrypted) {
|
||||
newObjectMessage.encrypt(message.to!!.pubkey!!)
|
||||
}
|
||||
doProofOfWork(message.to, newObjectMessage)
|
||||
}
|
||||
powRepo.removeObject(initialHash)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(ProofOfWorkService::class.java)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.constants
|
||||
|
||||
/**
|
||||
* Some network constants
|
||||
*/
|
||||
object Network {
|
||||
@JvmField val NETWORK_MAGIC_NUMBER = 8
|
||||
@JvmField val HEADER_SIZE = 24
|
||||
@JvmField val MAX_PAYLOAD_SIZE = 1600003
|
||||
@JvmField val MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'addr' command holds a list of known active Bitmessage nodes.
|
||||
*/
|
||||
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.ADDR
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(addresses.size, out)
|
||||
for (address in addresses) {
|
||||
address.write(out)
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(addresses.size, buffer)
|
||||
for (address in addresses) {
|
||||
address.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.utils.AccessCounter
|
||||
import ch.dissem.bitmessage.utils.Base58
|
||||
import ch.dissem.bitmessage.utils.Bytes
|
||||
import ch.dissem.bitmessage.utils.Decode.bytes
|
||||
import ch.dissem.bitmessage.utils.Decode.varInt
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
|
||||
* holding private keys.
|
||||
*/
|
||||
class BitmessageAddress : Serializable {
|
||||
|
||||
val version: Long
|
||||
val stream: Long
|
||||
val ripe: ByteArray
|
||||
val tag: ByteArray?
|
||||
/**
|
||||
* The private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. It's easier to just create
|
||||
* it regardless of address version.
|
||||
*/
|
||||
val publicDecryptionKey: ByteArray
|
||||
|
||||
val address: String
|
||||
|
||||
var privateKey: PrivateKey? = null
|
||||
private set
|
||||
var pubkey: Pubkey? = null
|
||||
set(pubkey) {
|
||||
if (pubkey != null) {
|
||||
if (pubkey is V4Pubkey) {
|
||||
if (!Arrays.equals(tag, pubkey.tag))
|
||||
throw IllegalArgumentException("Pubkey has incompatible tag")
|
||||
}
|
||||
if (!Arrays.equals(ripe, pubkey.ripe))
|
||||
throw IllegalArgumentException("Pubkey has incompatible ripe")
|
||||
field = pubkey
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var alias: String? = null
|
||||
var isSubscribed: Boolean = false
|
||||
var isChan: Boolean = false
|
||||
|
||||
internal constructor(version: Long, stream: Long, ripe: ByteArray) {
|
||||
this.version = version
|
||||
this.stream = stream
|
||||
this.ripe = ripe
|
||||
|
||||
val os = ByteArrayOutputStream()
|
||||
Encode.varInt(version, os)
|
||||
Encode.varInt(stream, os)
|
||||
if (version < 4) {
|
||||
val checksum = cryptography().sha512(os.toByteArray(), ripe)
|
||||
this.tag = null
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||
} else {
|
||||
// for tag and decryption key, the checksum has to be created with 0x00 padding
|
||||
val checksum = cryptography().doubleSha512(os.toByteArray(), ripe)
|
||||
this.tag = Arrays.copyOfRange(checksum, 32, 64)
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||
}
|
||||
// but for the address and its checksum they need to be stripped
|
||||
val offset = Bytes.numberOfLeadingZeros(ripe)
|
||||
os.write(ripe, offset, ripe.size - offset)
|
||||
val checksum = cryptography().doubleSha512(os.toByteArray())
|
||||
os.write(checksum, 0, 4)
|
||||
this.address = "BM-" + Base58.encode(os.toByteArray())
|
||||
}
|
||||
|
||||
constructor(publicKey: Pubkey) : this(publicKey.version, publicKey.stream, publicKey.ripe) {
|
||||
this.pubkey = publicKey
|
||||
}
|
||||
|
||||
constructor(address: String, passphrase: String) : this(address) {
|
||||
val key = PrivateKey(this, passphrase)
|
||||
if (!Arrays.equals(ripe, key.pubkey.ripe)) {
|
||||
throw IllegalArgumentException("Wrong address or passphrase")
|
||||
}
|
||||
this.privateKey = key
|
||||
this.pubkey = key.pubkey
|
||||
}
|
||||
|
||||
constructor(privateKey: PrivateKey) : this(privateKey.pubkey) {
|
||||
this.privateKey = privateKey
|
||||
}
|
||||
|
||||
constructor(address: String) {
|
||||
this.address = address
|
||||
val bytes = Base58.decode(address.substring(3))
|
||||
val `in` = ByteArrayInputStream(bytes)
|
||||
val counter = AccessCounter()
|
||||
this.version = varInt(`in`, counter)
|
||||
this.stream = varInt(`in`, counter)
|
||||
this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20)
|
||||
|
||||
// test checksum
|
||||
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
|
||||
val expectedChecksum = bytes(`in`, 4)
|
||||
for (i in 0..3) {
|
||||
if (expectedChecksum[i] != checksum[i])
|
||||
throw IllegalArgumentException("Checksum of address failed")
|
||||
}
|
||||
if (version < 4) {
|
||||
checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe)
|
||||
this.tag = null
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||
} else {
|
||||
checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe)
|
||||
this.tag = Arrays.copyOfRange(checksum, 32, 64)
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return alias ?: address
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is BitmessageAddress) return false
|
||||
return version == other.version &&
|
||||
stream == other.stream &&
|
||||
Arrays.equals(ripe, other.ripe)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Arrays.hashCode(ripe)
|
||||
}
|
||||
|
||||
fun has(feature: Feature?): Boolean {
|
||||
return feature?.isActive(pubkey?.behaviorBitfield ?: 0) ?: false
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun chan(address: String, passphrase: String): BitmessageAddress {
|
||||
val result = BitmessageAddress(address, passphrase)
|
||||
result.isChan = true
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic fun chan(stream: Long, passphrase: String): BitmessageAddress {
|
||||
val privateKey = PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase)
|
||||
val result = BitmessageAddress(privateKey)
|
||||
result.isChan = true
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int,
|
||||
version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
|
||||
val result = ArrayList<BitmessageAddress>(numberOfAddresses)
|
||||
val privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter)
|
||||
for (pk in privateKeys) {
|
||||
result.add(BitmessageAddress(pk))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic fun calculateTag(version: Long, stream: Long, ripe: ByteArray): ByteArray {
|
||||
val out = ByteArrayOutputStream()
|
||||
Encode.varInt(version, out)
|
||||
Encode.varInt(stream, out)
|
||||
out.write(ripe)
|
||||
return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.utils.AccessCounter
|
||||
import ch.dissem.bitmessage.utils.Decode.bytes
|
||||
import ch.dissem.bitmessage.utils.Decode.varString
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
open class CustomMessage(val customCommand: String, private val data: ByteArray? = null) : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM
|
||||
|
||||
val isError: Boolean
|
||||
|
||||
fun getData(): ByteArray {
|
||||
if (data != null) {
|
||||
return data
|
||||
} else {
|
||||
val out = ByteArrayOutputStream()
|
||||
write(out)
|
||||
return out.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
if (data != null) {
|
||||
Encode.varString(customCommand, out)
|
||||
out.write(data)
|
||||
} else {
|
||||
throw ApplicationException("Tried to write custom message without data. "
|
||||
+ "Programmer: did you forget to override #write()?")
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
if (data != null) {
|
||||
Encode.varString(customCommand, buffer)
|
||||
buffer.put(data)
|
||||
} else {
|
||||
throw ApplicationException("Tried to write custom message without data. "
|
||||
+ "Programmer: did you forget to override #write()?")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val COMMAND_ERROR = "ERROR"
|
||||
|
||||
@JvmStatic
|
||||
fun read(`in`: InputStream, length: Int): CustomMessage {
|
||||
val counter = AccessCounter()
|
||||
return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length()))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun error(message: String): CustomMessage {
|
||||
return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
this.isError = COMMAND_ERROR == customCommand
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
|
||||
/**
|
||||
* Used for objects that have encrypted content
|
||||
*/
|
||||
interface Encrypted {
|
||||
fun encrypt(publicKey: ByteArray)
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
fun decrypt(privateKey: ByteArray)
|
||||
|
||||
val isDecrypted: Boolean
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'getdata' command is used to request objects from a node.
|
||||
*/
|
||||
class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.GETDATA
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(inventory.size, out)
|
||||
for (iv in inventory) {
|
||||
iv.write(out)
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(inventory.size, buffer)
|
||||
for (iv in inventory) {
|
||||
iv.write(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val MAX_INVENTORY_SIZE = 50000
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
|
||||
*/
|
||||
class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.INV
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(inventory.size, out)
|
||||
for (iv in inventory) {
|
||||
iv.write(out)
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(inventory.size, buffer)
|
||||
for (iv in inventory) {
|
||||
iv.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
/**
|
||||
* A command can hold a network message payload
|
||||
*/
|
||||
interface MessagePayload : Streamable {
|
||||
val command: Command
|
||||
|
||||
enum class Command {
|
||||
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* A network message is exchanged between two nodes.
|
||||
*/
|
||||
data class NetworkMessage(
|
||||
/**
|
||||
* The actual data, a message or an object. Not to be confused with objectPayload.
|
||||
*/
|
||||
val payload: MessagePayload
|
||||
) : Streamable {
|
||||
|
||||
/**
|
||||
* First 4 bytes of sha512(payload)
|
||||
*/
|
||||
private fun getChecksum(bytes: ByteArray): ByteArray {
|
||||
val d = cryptography().sha512(bytes)
|
||||
return byteArrayOf(d[0], d[1], d[2], d[3])
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
// magic
|
||||
Encode.int32(MAGIC, out)
|
||||
|
||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||
val command = payload.command.name.toLowerCase()
|
||||
out.write(command.toByteArray(charset("ASCII")))
|
||||
for (i in command.length..11) {
|
||||
out.write(0x0)
|
||||
}
|
||||
|
||||
val payloadBytes = Encode.bytes(payload)
|
||||
|
||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||
// larger than this.
|
||||
Encode.int32(payloadBytes.size, out)
|
||||
|
||||
// checksum
|
||||
out.write(getChecksum(payloadBytes))
|
||||
|
||||
// message payload
|
||||
out.write(payloadBytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* A more efficient implementation of the write method, writing header data to the provided buffer and returning
|
||||
* a new buffer containing the payload.
|
||||
|
||||
* @param headerBuffer where the header data is written to (24 bytes)
|
||||
* *
|
||||
* @return a buffer containing the payload, ready to be read.
|
||||
*/
|
||||
fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer {
|
||||
return ByteBuffer.wrap(writeHeader(headerBuffer))
|
||||
}
|
||||
|
||||
/**
|
||||
* For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer]
|
||||
* and write the header buffer as well as the returned payload buffer into the channel.
|
||||
|
||||
* @param buffer where everything gets written to. Needs to be large enough for the whole message
|
||||
* * to be written.
|
||||
*/
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
val payloadBytes = writeHeader(buffer)
|
||||
buffer.put(payloadBytes)
|
||||
}
|
||||
|
||||
private fun writeHeader(out: ByteBuffer): ByteArray {
|
||||
// magic
|
||||
Encode.int32(MAGIC, out)
|
||||
|
||||
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
|
||||
val command = payload.command.name.toLowerCase()
|
||||
out.put(command.toByteArray(charset("ASCII")))
|
||||
|
||||
for (i in command.length..11) {
|
||||
out.put(0.toByte())
|
||||
}
|
||||
|
||||
val payloadBytes = Encode.bytes(payload)
|
||||
|
||||
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
|
||||
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
|
||||
// larger than this.
|
||||
Encode.int32(payloadBytes.size, out)
|
||||
|
||||
// checksum
|
||||
out.put(getChecksum(payloadBytes))
|
||||
|
||||
// message payload
|
||||
return payloadBytes
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
|
||||
*/
|
||||
val MAGIC = 0xE9BEB4D9.toInt()
|
||||
val MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array()
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.utils.Bytes
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* The 'object' command sends an object that is shared throughout the network.
|
||||
*/
|
||||
data class ObjectMessage(
|
||||
var nonce: ByteArray? = null,
|
||||
val expiresTime: Long,
|
||||
val payload: ObjectPayload,
|
||||
val type: Long,
|
||||
/**
|
||||
* The object's version
|
||||
*/
|
||||
val version: Long,
|
||||
val stream: Long
|
||||
) : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
|
||||
|
||||
constructor(
|
||||
nonce: ByteArray? = null,
|
||||
expiresTime: Long,
|
||||
payload: ObjectPayload,
|
||||
stream: Long
|
||||
) : this(
|
||||
nonce,
|
||||
expiresTime,
|
||||
payload,
|
||||
payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
|
||||
payload.version,
|
||||
stream
|
||||
)
|
||||
|
||||
val inventoryVector: InventoryVector
|
||||
get() {
|
||||
return InventoryVector(Bytes.truncate(cryptography().doubleSha512(
|
||||
nonce ?: throw IllegalStateException("nonce must be set"),
|
||||
payloadBytesWithoutNonce
|
||||
), 32))
|
||||
}
|
||||
|
||||
private val isEncrypted: Boolean
|
||||
get() = payload is Encrypted && !payload.isDecrypted
|
||||
|
||||
val isSigned: Boolean
|
||||
get() = payload.isSigned
|
||||
|
||||
private val bytesToSign: ByteArray
|
||||
get() {
|
||||
try {
|
||||
val out = ByteArrayOutputStream()
|
||||
writeHeaderWithoutNonce(out)
|
||||
payload.writeBytesToSign(out)
|
||||
return out.toByteArray()
|
||||
} catch (e: IOException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun sign(key: PrivateKey) {
|
||||
if (payload.isSigned) {
|
||||
payload.signature = cryptography().getSignature(bytesToSign, key)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
fun decrypt(key: PrivateKey) {
|
||||
if (payload is Encrypted) {
|
||||
payload.decrypt(key.privateEncryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
fun decrypt(privateEncryptionKey: ByteArray) {
|
||||
if (payload is Encrypted) {
|
||||
payload.decrypt(privateEncryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun encrypt(publicEncryptionKey: ByteArray) {
|
||||
if (payload is Encrypted) {
|
||||
payload.encrypt(publicEncryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun encrypt(publicKey: Pubkey) {
|
||||
try {
|
||||
if (payload is Encrypted) {
|
||||
payload.encrypt(publicKey.encryptionKey)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun isSignatureValid(pubkey: Pubkey): Boolean {
|
||||
if (isEncrypted) throw IllegalStateException("Payload must be decrypted first")
|
||||
return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(nonce ?: ByteArray(8))
|
||||
out.write(payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(nonce ?: ByteArray(8))
|
||||
buffer.put(payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
private fun writeHeaderWithoutNonce(out: OutputStream) {
|
||||
Encode.int64(expiresTime, out)
|
||||
Encode.int32(type, out)
|
||||
Encode.varInt(version, out)
|
||||
Encode.varInt(stream, out)
|
||||
}
|
||||
|
||||
val payloadBytesWithoutNonce: ByteArray by lazy {
|
||||
val out = ByteArrayOutputStream()
|
||||
writeHeaderWithoutNonce(out)
|
||||
payload.write(out)
|
||||
out.toByteArray()
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var nonce: ByteArray? = null
|
||||
private var expiresTime: Long = 0
|
||||
private var objectType: Long? = null
|
||||
private var streamNumber: Long = 0
|
||||
private var payload: ObjectPayload? = null
|
||||
|
||||
fun nonce(nonce: ByteArray): Builder {
|
||||
this.nonce = nonce
|
||||
return this
|
||||
}
|
||||
|
||||
fun expiresTime(expiresTime: Long): Builder {
|
||||
this.expiresTime = expiresTime
|
||||
return this
|
||||
}
|
||||
|
||||
fun objectType(objectType: Long): Builder {
|
||||
this.objectType = objectType
|
||||
return this
|
||||
}
|
||||
|
||||
fun objectType(objectType: ObjectType): Builder {
|
||||
this.objectType = objectType.number
|
||||
return this
|
||||
}
|
||||
|
||||
fun stream(streamNumber: Long): Builder {
|
||||
this.streamNumber = streamNumber
|
||||
return this
|
||||
}
|
||||
|
||||
fun payload(payload: ObjectPayload): Builder {
|
||||
this.payload = payload
|
||||
if (this.objectType == null)
|
||||
this.objectType = payload.type?.number
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): ObjectMessage {
|
||||
return ObjectMessage(
|
||||
nonce = nonce,
|
||||
expiresTime = expiresTime,
|
||||
type = objectType!!,
|
||||
version = payload!!.version,
|
||||
stream = if (streamNumber > 0) streamNumber else payload!!.stream,
|
||||
payload = payload!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is ObjectMessage) return false
|
||||
return expiresTime == other.expiresTime &&
|
||||
type == other.type &&
|
||||
version == other.version &&
|
||||
stream == other.stream &&
|
||||
payload == other.payload
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = Arrays.hashCode(nonce)
|
||||
result = 31 * result + (expiresTime xor expiresTime.ushr(32)).toInt()
|
||||
result = 31 * result + (type xor type.ushr(32)).toInt()
|
||||
result = 31 * result + (version xor version.ushr(32)).toInt()
|
||||
result = 31 * result + (stream xor stream.ushr(32)).toInt()
|
||||
result = 31 * result + (payload.hashCode())
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,768 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Encoding.*
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||
import ch.dissem.bitmessage.entity.payload.Msg
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
|
||||
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import ch.dissem.bitmessage.entity.valueobject.extended.Attachment
|
||||
import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.factory.ExtendedEncodingFactory
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.utils.*
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.Collections
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) {
|
||||
SIMPLE -> "Subject:$subject\nBody:$body".toByteArray()
|
||||
EXTENDED -> Message.Builder().subject(subject).body(body).build().zip()
|
||||
TRIVIAL -> (subject + body).toByteArray()
|
||||
IGNORE -> ByteArray(0)
|
||||
}
|
||||
|
||||
private fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? {
|
||||
if (ackData != null) {
|
||||
return ackData
|
||||
} else if (type == MSG) {
|
||||
return cryptography().randomBytes(Msg.ACK_LENGTH)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A plaintext message before encryption or after decryption.
|
||||
*/
|
||||
class Plaintext private constructor(
|
||||
val type: Type,
|
||||
val from: BitmessageAddress,
|
||||
to: BitmessageAddress?,
|
||||
val encodingCode: Long,
|
||||
val message: ByteArray,
|
||||
val ackData: ByteArray?,
|
||||
ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) },
|
||||
val conversationId: UUID = UUID.randomUUID(),
|
||||
var inventoryVector: InventoryVector? = null,
|
||||
var signature: ByteArray? = null,
|
||||
sent: Long? = null,
|
||||
val received: Long? = null,
|
||||
var initialHash: ByteArray? = null,
|
||||
ttl: Long = TTL.msg,
|
||||
val labels: MutableSet<Label> = HashSet(),
|
||||
status: Status
|
||||
) : Streamable {
|
||||
|
||||
var id: Any? = null
|
||||
set(id) {
|
||||
if (this.id != null) throw IllegalStateException("ID already set")
|
||||
field = id
|
||||
}
|
||||
|
||||
var to: BitmessageAddress? = to
|
||||
set(to) {
|
||||
if (to == null) {
|
||||
return
|
||||
}
|
||||
if (this.to != null) {
|
||||
if (this.to!!.version != 0L)
|
||||
throw IllegalStateException("Correct address already set")
|
||||
if (!Arrays.equals(this.to!!.ripe, to.ripe)) {
|
||||
throw IllegalArgumentException("RIPEs don't match")
|
||||
}
|
||||
}
|
||||
field = to
|
||||
}
|
||||
|
||||
val stream: Long
|
||||
get() = to?.stream ?: from.stream
|
||||
|
||||
val extendedData: ExtendedEncoding? by lazy {
|
||||
if (encodingCode == EXTENDED.code) {
|
||||
ExtendedEncodingFactory.unzip(message)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val ackMessage: ObjectMessage? by ackMessage
|
||||
|
||||
var status: Status = status
|
||||
set(status) {
|
||||
if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) {
|
||||
sent = UnixTime.now
|
||||
}
|
||||
field = status
|
||||
}
|
||||
|
||||
val encoding: Encoding? by lazy { Encoding.fromCode(encodingCode) }
|
||||
var sent: Long? = sent
|
||||
private set
|
||||
var retries: Int = 0
|
||||
private set
|
||||
var nextTry: Long? = null
|
||||
private set
|
||||
val ttl: Long = ttl
|
||||
@JvmName("getTTL") get
|
||||
|
||||
constructor(
|
||||
type: Type,
|
||||
from: BitmessageAddress,
|
||||
to: BitmessageAddress?,
|
||||
encoding: Encoding,
|
||||
message: ByteArray,
|
||||
ackData: ByteArray? = null,
|
||||
conversationId: UUID = UUID.randomUUID(),
|
||||
inventoryVector: InventoryVector? = null,
|
||||
signature: ByteArray? = null,
|
||||
received: Long? = null,
|
||||
initialHash: ByteArray? = null,
|
||||
ttl: Long = TTL.msg,
|
||||
labels: MutableSet<Label> = HashSet(),
|
||||
status: Status
|
||||
) : this(
|
||||
type = type,
|
||||
from = from,
|
||||
to = to,
|
||||
encodingCode = encoding.code,
|
||||
message = message,
|
||||
ackData = ackData(type, ackData),
|
||||
conversationId = conversationId,
|
||||
inventoryVector = inventoryVector,
|
||||
signature = signature,
|
||||
received = received,
|
||||
initialHash = initialHash,
|
||||
ttl = ttl,
|
||||
labels = labels,
|
||||
status = status
|
||||
)
|
||||
|
||||
constructor(
|
||||
type: Type,
|
||||
from: BitmessageAddress,
|
||||
to: BitmessageAddress?,
|
||||
encoding: Long,
|
||||
message: ByteArray,
|
||||
ackMessage: ByteArray?,
|
||||
conversationId: UUID = UUID.randomUUID(),
|
||||
inventoryVector: InventoryVector? = null,
|
||||
signature: ByteArray? = null,
|
||||
received: Long? = null,
|
||||
initialHash: ByteArray? = null,
|
||||
ttl: Long = TTL.msg,
|
||||
labels: MutableSet<Label> = HashSet(),
|
||||
status: Status
|
||||
) : this(
|
||||
type = type,
|
||||
from = from,
|
||||
to = to,
|
||||
encodingCode = encoding,
|
||||
message = message,
|
||||
ackData = null,
|
||||
ackMessage = lazy {
|
||||
if (ackMessage != null && ackMessage.isNotEmpty()) {
|
||||
Factory.getObjectMessage(
|
||||
3,
|
||||
ByteArrayInputStream(ackMessage),
|
||||
ackMessage.size)
|
||||
} else null
|
||||
},
|
||||
conversationId = conversationId,
|
||||
inventoryVector = inventoryVector,
|
||||
signature = signature,
|
||||
received = received,
|
||||
initialHash = initialHash,
|
||||
ttl = ttl,
|
||||
labels = labels,
|
||||
status = status
|
||||
)
|
||||
|
||||
constructor(
|
||||
type: Type,
|
||||
from: BitmessageAddress,
|
||||
to: BitmessageAddress? = null,
|
||||
encoding: Encoding = SIMPLE,
|
||||
subject: String,
|
||||
body: String,
|
||||
ackData: ByteArray? = null,
|
||||
conversationId: UUID = UUID.randomUUID(),
|
||||
ttl: Long = TTL.msg,
|
||||
labels: MutableSet<Label> = HashSet(),
|
||||
status: Status = Status.DRAFT
|
||||
) : this(
|
||||
type = type,
|
||||
from = from,
|
||||
to = to,
|
||||
encoding = encoding,
|
||||
message = message(encoding, subject, body),
|
||||
ackData = ackData(type, ackData),
|
||||
conversationId = conversationId,
|
||||
inventoryVector = null,
|
||||
signature = null,
|
||||
received = null,
|
||||
initialHash = null,
|
||||
ttl = ttl,
|
||||
labels = labels,
|
||||
status = status
|
||||
)
|
||||
|
||||
constructor(builder: Builder) : this(
|
||||
// Calling prepare() here is somewhat ugly, but also a foolproof way to make sure the builder is properly initialized
|
||||
type = builder.prepare().type,
|
||||
from = builder.from ?: throw IllegalStateException("sender identity not set"),
|
||||
to = builder.to,
|
||||
encodingCode = builder.encoding,
|
||||
message = builder.message,
|
||||
ackData = builder.ackData,
|
||||
ackMessage = lazy {
|
||||
val ackMsg = builder.ackMessage
|
||||
if (ackMsg != null && ackMsg.isNotEmpty()) {
|
||||
Factory.getObjectMessage(
|
||||
3,
|
||||
ByteArrayInputStream(ackMsg),
|
||||
ackMsg.size)
|
||||
} else {
|
||||
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
|
||||
}
|
||||
},
|
||||
conversationId = builder.conversation ?: UUID.randomUUID(),
|
||||
inventoryVector = builder.inventoryVector,
|
||||
signature = builder.signature,
|
||||
sent = builder.sent,
|
||||
received = builder.received,
|
||||
initialHash = null,
|
||||
ttl = builder.ttl,
|
||||
labels = builder.labels,
|
||||
status = builder.status ?: Status.RECEIVED
|
||||
) {
|
||||
id = builder.id
|
||||
}
|
||||
|
||||
fun write(out: OutputStream, includeSignature: Boolean) {
|
||||
Encode.varInt(from.version, out)
|
||||
Encode.varInt(from.stream, out)
|
||||
from.pubkey?.apply {
|
||||
Encode.int32(behaviorBitfield, out)
|
||||
out.write(signingKey, 1, 64)
|
||||
out.write(encryptionKey, 1, 64)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(nonceTrialsPerByte, out)
|
||||
Encode.varInt(extraBytes, out)
|
||||
}
|
||||
} ?: {
|
||||
Encode.int32(0, out)
|
||||
val empty = ByteArray(64)
|
||||
out.write(empty)
|
||||
out.write(empty)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(0, out)
|
||||
Encode.varInt(0, out)
|
||||
}
|
||||
}.invoke()
|
||||
if (type == MSG) {
|
||||
out.write(to?.ripe ?: throw IllegalStateException("No recipient set for message"))
|
||||
}
|
||||
Encode.varInt(encodingCode, out)
|
||||
Encode.varInt(message.size, out)
|
||||
out.write(message)
|
||||
if (type == MSG) {
|
||||
if (to?.has(Feature.DOES_ACK) ?: false) {
|
||||
val ack = ByteArrayOutputStream()
|
||||
ackMessage?.write(ack)
|
||||
Encode.varBytes(ack.toByteArray(), out)
|
||||
} else {
|
||||
Encode.varInt(0, out)
|
||||
}
|
||||
}
|
||||
if (includeSignature) {
|
||||
if (signature == null) {
|
||||
Encode.varInt(0, out)
|
||||
} else {
|
||||
Encode.varBytes(signature!!, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun write(buffer: ByteBuffer, includeSignature: Boolean) {
|
||||
Encode.varInt(from.version, buffer)
|
||||
Encode.varInt(from.stream, buffer)
|
||||
if (from.pubkey == null) {
|
||||
Encode.int32(0, buffer)
|
||||
val empty = ByteArray(64)
|
||||
buffer.put(empty)
|
||||
buffer.put(empty)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(0, buffer)
|
||||
Encode.varInt(0, buffer)
|
||||
}
|
||||
} else {
|
||||
Encode.int32(from.pubkey!!.behaviorBitfield, buffer)
|
||||
buffer.put(from.pubkey!!.signingKey, 1, 64)
|
||||
buffer.put(from.pubkey!!.encryptionKey, 1, 64)
|
||||
if (from.version >= 3) {
|
||||
Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer)
|
||||
Encode.varInt(from.pubkey!!.extraBytes, buffer)
|
||||
}
|
||||
}
|
||||
if (type == MSG) {
|
||||
buffer.put(to!!.ripe)
|
||||
}
|
||||
Encode.varInt(encodingCode, buffer)
|
||||
Encode.varBytes(message, buffer)
|
||||
if (type == MSG) {
|
||||
if (to!!.has(Feature.DOES_ACK) && ackMessage != null) {
|
||||
Encode.varBytes(Encode.bytes(ackMessage!!), buffer)
|
||||
} else {
|
||||
Encode.varInt(0, buffer)
|
||||
}
|
||||
}
|
||||
if (includeSignature) {
|
||||
val sig = signature
|
||||
if (sig == null) {
|
||||
Encode.varInt(0, buffer)
|
||||
} else {
|
||||
Encode.varBytes(sig, buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
write(out, true)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
write(buffer, true)
|
||||
}
|
||||
|
||||
fun updateNextTry() {
|
||||
if (to != null) {
|
||||
if (nextTry == null) {
|
||||
if (sent != null && to!!.has(Feature.DOES_ACK)) {
|
||||
nextTry = UnixTime.now + ttl
|
||||
retries++
|
||||
}
|
||||
} else {
|
||||
nextTry = nextTry!! + (1 shl retries) * ttl
|
||||
retries++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val subject: String?
|
||||
get() {
|
||||
val s = Scanner(ByteArrayInputStream(message), "UTF-8")
|
||||
val firstLine = s.nextLine()
|
||||
if (encodingCode == EXTENDED.code) {
|
||||
if (Message.TYPE == extendedData?.type) {
|
||||
return (extendedData!!.content as? Message)?.subject
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else if (encodingCode == SIMPLE.code) {
|
||||
return firstLine.substring("Subject:".length).trim { it <= ' ' }
|
||||
} else if (firstLine.length > 50) {
|
||||
return firstLine.substring(0, 50).trim { it <= ' ' } + "..."
|
||||
} else {
|
||||
return firstLine
|
||||
}
|
||||
}
|
||||
|
||||
val text: String?
|
||||
get() {
|
||||
if (encodingCode == EXTENDED.code) {
|
||||
if (Message.TYPE == extendedData?.type) {
|
||||
return (extendedData?.content as Message?)?.body
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
val text = String(message)
|
||||
if (encodingCode == SIMPLE.code) {
|
||||
return text.substring(text.indexOf("\nBody:") + 6)
|
||||
}
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : ExtendedEncoding.ExtendedType> getExtendedData(type: Class<T>): T? {
|
||||
val extendedData = extendedData ?: return null
|
||||
if (type.isInstance(extendedData.content)) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return extendedData.content as T
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val parents: List<InventoryVector>
|
||||
get() {
|
||||
val extendedData = extendedData ?: return emptyList()
|
||||
if (Message.TYPE == extendedData.type) {
|
||||
return (extendedData.content as Message).parents
|
||||
} else {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
val files: List<Attachment>
|
||||
get() {
|
||||
val extendedData = extendedData ?: return emptyList()
|
||||
if (Message.TYPE == extendedData.type) {
|
||||
return (extendedData.content as Message).files
|
||||
} else {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Plaintext) return false
|
||||
return encoding == other.encoding &&
|
||||
from.address == other.from.address &&
|
||||
Arrays.equals(message, other.message) &&
|
||||
ackMessage == other.ackMessage &&
|
||||
Arrays.equals(to?.ripe, other.to?.ripe) &&
|
||||
Arrays.equals(signature, other.signature) &&
|
||||
status == other.status &&
|
||||
sent == other.sent &&
|
||||
received == other.received &&
|
||||
labels == other.labels
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels)
|
||||
}
|
||||
|
||||
fun addLabels(vararg labels: Label) {
|
||||
Collections.addAll(this.labels, *labels)
|
||||
}
|
||||
|
||||
fun addLabels(labels: Collection<Label>?) {
|
||||
if (labels != null) {
|
||||
this.labels.addAll(labels)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeLabel(type: Label.Type) {
|
||||
labels.removeAll { it.type == type }
|
||||
}
|
||||
|
||||
fun isUnread(): Boolean {
|
||||
return labels.any { it.type == Label.Type.UNREAD }
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val subject = subject
|
||||
if (subject?.isNotEmpty() ?: false) {
|
||||
return subject!!
|
||||
} else {
|
||||
return Strings.hex(
|
||||
initialHash ?: return super.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Encoding constructor(code: Long) {
|
||||
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
|
||||
|
||||
var code: Long = 0
|
||||
internal set
|
||||
|
||||
init {
|
||||
this.code = code
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic fun fromCode(code: Long): Encoding? {
|
||||
for (e in values()) {
|
||||
if (e.code == code) {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
DRAFT,
|
||||
// For sent messages
|
||||
PUBKEY_REQUESTED,
|
||||
DOING_PROOF_OF_WORK,
|
||||
SENT,
|
||||
SENT_ACKNOWLEDGED,
|
||||
RECEIVED
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
MSG, BROADCAST
|
||||
}
|
||||
|
||||
class Builder(internal val type: Type) {
|
||||
internal var id: Any? = null
|
||||
internal var inventoryVector: InventoryVector? = null
|
||||
internal var from: BitmessageAddress? = null
|
||||
internal var to: BitmessageAddress? = null
|
||||
private var addressVersion: Long = 0
|
||||
private var stream: Long = 0
|
||||
private var behaviorBitfield: Int = 0
|
||||
private var publicSigningKey: ByteArray? = null
|
||||
private var publicEncryptionKey: ByteArray? = null
|
||||
private var nonceTrialsPerByte: Long = 0
|
||||
private var extraBytes: Long = 0
|
||||
private var destinationRipe: ByteArray? = null
|
||||
private var preventAck: Boolean = false
|
||||
internal var encoding: Long = 0
|
||||
internal var message = ByteArray(0)
|
||||
internal var ackData: ByteArray? = null
|
||||
internal var ackMessage: ByteArray? = null
|
||||
internal var signature: ByteArray? = null
|
||||
internal var sent: Long? = null
|
||||
internal var received: Long? = null
|
||||
internal var status: Status? = null
|
||||
internal val labels = LinkedHashSet<Label>()
|
||||
internal var ttl: Long = 0
|
||||
internal var retries: Int = 0
|
||||
internal var nextTry: Long? = null
|
||||
internal var conversation: UUID? = null
|
||||
|
||||
fun id(id: Any): Builder {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
||||
fun IV(iv: InventoryVector?): Builder {
|
||||
this.inventoryVector = iv
|
||||
return this
|
||||
}
|
||||
|
||||
fun from(address: BitmessageAddress): Builder {
|
||||
from = address
|
||||
return this
|
||||
}
|
||||
|
||||
fun to(address: BitmessageAddress?): Builder {
|
||||
if (address != null) {
|
||||
if (type != MSG && to != null)
|
||||
throw IllegalArgumentException("recipient address only allowed for msg")
|
||||
to = address
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun addressVersion(addressVersion: Long): Builder {
|
||||
this.addressVersion = addressVersion
|
||||
return this
|
||||
}
|
||||
|
||||
fun stream(stream: Long): Builder {
|
||||
this.stream = stream
|
||||
return this
|
||||
}
|
||||
|
||||
fun behaviorBitfield(behaviorBitfield: Int): Builder {
|
||||
this.behaviorBitfield = behaviorBitfield
|
||||
return this
|
||||
}
|
||||
|
||||
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
|
||||
this.publicSigningKey = publicSigningKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
|
||||
this.publicEncryptionKey = publicEncryptionKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
|
||||
this.nonceTrialsPerByte = nonceTrialsPerByte
|
||||
return this
|
||||
}
|
||||
|
||||
fun extraBytes(extraBytes: Long): Builder {
|
||||
this.extraBytes = extraBytes
|
||||
return this
|
||||
}
|
||||
|
||||
fun destinationRipe(ripe: ByteArray?): Builder {
|
||||
if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg")
|
||||
this.destinationRipe = ripe
|
||||
return this
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun preventAck(preventAck: Boolean = true): Builder {
|
||||
this.preventAck = preventAck
|
||||
return this
|
||||
}
|
||||
|
||||
fun encoding(encoding: Encoding): Builder {
|
||||
this.encoding = encoding.code
|
||||
return this
|
||||
}
|
||||
|
||||
fun encoding(encoding: Long): Builder {
|
||||
this.encoding = encoding
|
||||
return this
|
||||
}
|
||||
|
||||
fun message(message: ExtendedEncoding): Builder {
|
||||
this.encoding = EXTENDED.code
|
||||
this.message = message.zip()
|
||||
return this
|
||||
}
|
||||
|
||||
fun message(subject: String, message: String): Builder {
|
||||
try {
|
||||
this.encoding = SIMPLE.code
|
||||
this.message = "Subject:$subject\nBody:$message".toByteArray(charset("UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun message(message: ByteArray): Builder {
|
||||
this.message = message
|
||||
return this
|
||||
}
|
||||
|
||||
fun ackMessage(ack: ByteArray?): Builder {
|
||||
if (type != MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg")
|
||||
this.ackMessage = ack
|
||||
return this
|
||||
}
|
||||
|
||||
fun ackData(ackData: ByteArray?): Builder {
|
||||
if (type != MSG && ackData != null)
|
||||
throw IllegalArgumentException("ackMessage only allowed for msg")
|
||||
this.ackData = ackData
|
||||
return this
|
||||
}
|
||||
|
||||
fun signature(signature: ByteArray?): Builder {
|
||||
this.signature = signature
|
||||
return this
|
||||
}
|
||||
|
||||
fun sent(sent: Long?): Builder {
|
||||
this.sent = sent
|
||||
return this
|
||||
}
|
||||
|
||||
fun received(received: Long?): Builder {
|
||||
this.received = received
|
||||
return this
|
||||
}
|
||||
|
||||
fun status(status: Status): Builder {
|
||||
this.status = status
|
||||
return this
|
||||
}
|
||||
|
||||
fun labels(labels: Collection<Label>): Builder {
|
||||
this.labels.addAll(labels)
|
||||
return this
|
||||
}
|
||||
|
||||
fun ttl(ttl: Long): Builder {
|
||||
this.ttl = ttl
|
||||
return this
|
||||
}
|
||||
|
||||
fun retries(retries: Int): Builder {
|
||||
this.retries = retries
|
||||
return this
|
||||
}
|
||||
|
||||
fun nextTry(nextTry: Long?): Builder {
|
||||
this.nextTry = nextTry
|
||||
return this
|
||||
}
|
||||
|
||||
fun conversation(id: UUID): Builder {
|
||||
this.conversation = id
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun prepare(): Builder {
|
||||
if (from == null) {
|
||||
from = BitmessageAddress(Factory.createPubkey(
|
||||
addressVersion,
|
||||
stream,
|
||||
publicSigningKey!!,
|
||||
publicEncryptionKey!!,
|
||||
nonceTrialsPerByte,
|
||||
extraBytes,
|
||||
behaviorBitfield
|
||||
))
|
||||
}
|
||||
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
|
||||
to = BitmessageAddress(0, 0, destinationRipe!!)
|
||||
}
|
||||
if (preventAck) {
|
||||
ackData = null
|
||||
ackMessage = null
|
||||
} else if (type == MSG && ackMessage == null && ackData == null) {
|
||||
ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
|
||||
}
|
||||
if (ttl <= 0) {
|
||||
ttl = TTL.msg
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): Plaintext {
|
||||
return Plaintext(this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic fun read(type: Type, `in`: InputStream): Plaintext {
|
||||
return readWithoutSignature(type, `in`)
|
||||
.signature(Decode.varBytes(`in`))
|
||||
.received(UnixTime.now)
|
||||
.build()
|
||||
}
|
||||
|
||||
@JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder {
|
||||
val version = Decode.varInt(`in`)
|
||||
return Builder(type)
|
||||
.addressVersion(version)
|
||||
.stream(Decode.varInt(`in`))
|
||||
.behaviorBitfield(Decode.int32(`in`))
|
||||
.publicSigningKey(Decode.bytes(`in`, 64))
|
||||
.publicEncryptionKey(Decode.bytes(`in`, 64))
|
||||
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0)
|
||||
.extraBytes(if (version >= 3) Decode.varInt(`in`) else 0)
|
||||
.destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null)
|
||||
.encoding(Decode.varInt(`in`))
|
||||
.message(Decode.varBytes(`in`))
|
||||
.ackMessage(if (type == MSG) Decode.varBytes(`in`) else null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
interface PlaintextHolder {
|
||||
val plaintext: Plaintext?
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.io.Serializable
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* An object that can be written to an [OutputStream]
|
||||
*/
|
||||
interface Streamable : Serializable {
|
||||
fun write(out: OutputStream)
|
||||
|
||||
fun write(buffer: ByteBuffer)
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'verack' command answers a 'version' command, accepting the other node's version.
|
||||
*/
|
||||
class VerAck : MessagePayload {
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
// 'verack' doesn't have any payload, so there is nothing to write
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val serialVersionUID = -4302074845199181687L
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* The 'version' command advertises this node's latest supported protocol version upon initiation.
|
||||
*/
|
||||
class Version constructor(
|
||||
/**
|
||||
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
|
||||
* version is lower but continue with the connection if it is higher.
|
||||
*/
|
||||
val version: Int = BitmessageContext.CURRENT_VERSION,
|
||||
|
||||
/**
|
||||
* bitfield of features to be enabled for this connection
|
||||
*/
|
||||
val services: Long = Version.Service.getServiceFlag(Version.Service.NODE_NETWORK),
|
||||
|
||||
/**
|
||||
* standard UNIX timestamp in seconds
|
||||
*/
|
||||
val timestamp: Long = UnixTime.now,
|
||||
|
||||
/**
|
||||
* The network address of the node receiving this message (not including the time or stream number)
|
||||
*/
|
||||
val addrRecv: NetworkAddress,
|
||||
|
||||
/**
|
||||
* The network address of the node emitting this message (not including the time or stream number and the ip itself
|
||||
* is ignored by the receiver)
|
||||
*/
|
||||
val addrFrom: NetworkAddress,
|
||||
|
||||
/**
|
||||
* Random nonce used to detect connections to self.
|
||||
*/
|
||||
val nonce: Long,
|
||||
|
||||
/**
|
||||
* User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes.
|
||||
*/
|
||||
val userAgent: String,
|
||||
|
||||
/**
|
||||
* The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000
|
||||
* stream numbers.
|
||||
*/
|
||||
val streams: LongArray = longArrayOf(1)
|
||||
) : MessagePayload {
|
||||
|
||||
fun provides(service: Service?): Boolean {
|
||||
return service != null && service.isEnabled(services)
|
||||
}
|
||||
|
||||
override val command: MessagePayload.Command = MessagePayload.Command.VERSION
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.int32(version, out)
|
||||
Encode.int64(services, out)
|
||||
Encode.int64(timestamp, out)
|
||||
addrRecv.write(out, true)
|
||||
addrFrom.write(out, true)
|
||||
Encode.int64(nonce, out)
|
||||
Encode.varString(userAgent, out)
|
||||
Encode.varIntList(streams, out)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.int32(version, buffer)
|
||||
Encode.int64(services, buffer)
|
||||
Encode.int64(timestamp, buffer)
|
||||
addrRecv.write(buffer, true)
|
||||
addrFrom.write(buffer, true)
|
||||
Encode.int64(nonce, buffer)
|
||||
Encode.varString(userAgent, buffer)
|
||||
Encode.varIntList(streams, buffer)
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var version: Int = 0
|
||||
private var services: Long = 0
|
||||
private var timestamp: Long = 0
|
||||
private var addrRecv: NetworkAddress? = null
|
||||
private var addrFrom: NetworkAddress? = null
|
||||
private var nonce: Long = 0
|
||||
private var userAgent: String? = null
|
||||
private var streamNumbers: LongArray? = null
|
||||
|
||||
fun defaults(clientNonce: Long): Builder {
|
||||
version = BitmessageContext.CURRENT_VERSION
|
||||
services = Service.getServiceFlag(Service.NODE_NETWORK)
|
||||
timestamp = UnixTime.now
|
||||
userAgent = "/Jabit:0.0.1/"
|
||||
streamNumbers = longArrayOf(1)
|
||||
nonce = clientNonce
|
||||
return this
|
||||
}
|
||||
|
||||
fun version(version: Int): Builder {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
fun services(vararg services: Service): Builder {
|
||||
this.services = Service.getServiceFlag(*services)
|
||||
return this
|
||||
}
|
||||
|
||||
fun services(services: Long): Builder {
|
||||
this.services = services
|
||||
return this
|
||||
}
|
||||
|
||||
fun timestamp(timestamp: Long): Builder {
|
||||
this.timestamp = timestamp
|
||||
return this
|
||||
}
|
||||
|
||||
fun addrRecv(addrRecv: NetworkAddress): Builder {
|
||||
this.addrRecv = addrRecv
|
||||
return this
|
||||
}
|
||||
|
||||
fun addrFrom(addrFrom: NetworkAddress): Builder {
|
||||
this.addrFrom = addrFrom
|
||||
return this
|
||||
}
|
||||
|
||||
fun nonce(nonce: Long): Builder {
|
||||
this.nonce = nonce
|
||||
return this
|
||||
}
|
||||
|
||||
fun userAgent(userAgent: String): Builder {
|
||||
this.userAgent = userAgent
|
||||
return this
|
||||
}
|
||||
|
||||
fun streams(vararg streamNumbers: Long): Builder {
|
||||
this.streamNumbers = streamNumbers
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): Version {
|
||||
val addrRecv = this.addrRecv
|
||||
val addrFrom = this.addrFrom
|
||||
if (addrRecv == null || addrFrom == null) {
|
||||
throw IllegalStateException("Receiving and sending address must be set")
|
||||
}
|
||||
|
||||
return Version(
|
||||
version = version,
|
||||
services = services,
|
||||
timestamp = timestamp,
|
||||
addrRecv = addrRecv, addrFrom = addrFrom,
|
||||
nonce = nonce,
|
||||
userAgent = userAgent ?: "/Jabit:0.0.1/",
|
||||
streams = streamNumbers ?: longArrayOf(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Service constructor(internal var flag: Long) {
|
||||
// TODO: NODE_SSL(2);
|
||||
NODE_NETWORK(1);
|
||||
|
||||
fun isEnabled(flag: Long): Boolean {
|
||||
return (flag and this.flag) != 0L
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getServiceFlag(vararg services: Service): Long {
|
||||
var flag: Long = 0
|
||||
for (service in services) {
|
||||
flag = flag or service.flag
|
||||
}
|
||||
return flag
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Encrypted
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||
import ch.dissem.bitmessage.entity.PlaintextHolder
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
* Broadcasts are version 4 or 5.
|
||||
*/
|
||||
abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder {
|
||||
|
||||
override val isSigned: Boolean = true
|
||||
|
||||
override var signature: ByteArray?
|
||||
get() = plaintext?.signature
|
||||
set(signature) {
|
||||
plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
|
||||
override fun encrypt(publicKey: ByteArray) {
|
||||
this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey)
|
||||
}
|
||||
|
||||
fun encrypt() {
|
||||
encrypt(cryptography().createPublicKey(plaintext?.from?.publicDecryptionKey ?: return))
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
override fun decrypt(privateKey: ByteArray) {
|
||||
plaintext = Plaintext.read(BROADCAST, encrypted?.decrypt(privateKey) ?: return)
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
fun decrypt(address: BitmessageAddress) {
|
||||
decrypt(address.publicDecryptionKey)
|
||||
}
|
||||
|
||||
override val isDecrypted: Boolean
|
||||
get() = plaintext != null
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Broadcast) return false
|
||||
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(stream)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getVersion(address: BitmessageAddress): Long {
|
||||
return if (address.version < 4) 4L else 5L
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.utils.*
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
|
||||
class CryptoBox : Streamable {
|
||||
|
||||
private val initializationVector: ByteArray
|
||||
private val curveType: Int
|
||||
private val R: ByteArray
|
||||
private val mac: ByteArray
|
||||
private var encrypted: ByteArray
|
||||
|
||||
constructor(data: Streamable, K: ByteArray) : this(Encode.bytes(data), K)
|
||||
|
||||
constructor(data: ByteArray, K: ByteArray) {
|
||||
curveType = 0x02CA
|
||||
|
||||
// 1. The destination public key is called K.
|
||||
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
|
||||
initializationVector = cryptography().randomBytes(16)
|
||||
|
||||
// 3. Generate a new random EC key pair with private key called r and public key called R.
|
||||
val r = cryptography().randomBytes(PRIVATE_KEY_SIZE)
|
||||
R = cryptography().createPublicKey(r)
|
||||
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
|
||||
val P = cryptography().multiply(K, r)
|
||||
val X = Points.getX(P)
|
||||
// 5. Use the X component of public key P and calculate the SHA512 hash H.
|
||||
val H = cryptography().sha512(X)
|
||||
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||
val key_e = Arrays.copyOfRange(H, 0, 32)
|
||||
val key_m = Arrays.copyOfRange(H, 32, 64)
|
||||
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
|
||||
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
|
||||
encrypted = cryptography().crypt(true, data, key_e, initializationVector)
|
||||
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
|
||||
mac = calculateMac(key_m)
|
||||
|
||||
// The resulting data is: IV + R + cipher text + MAC
|
||||
}
|
||||
|
||||
private constructor(builder: Builder) {
|
||||
initializationVector = builder.initializationVector!!
|
||||
curveType = builder.curveType
|
||||
R = cryptography().createPoint(builder.xComponent!!, builder.yComponent!!)
|
||||
encrypted = builder.encrypted!!
|
||||
mac = builder.mac!!
|
||||
}
|
||||
|
||||
/**
|
||||
* @param k a private key, typically should be 32 bytes long
|
||||
* *
|
||||
* @return an InputStream yielding the decrypted data
|
||||
* *
|
||||
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
|
||||
* *
|
||||
* @see [https://bitmessage.org/wiki/Encryption.Decryption](https://bitmessage.org/wiki/Encryption.Decryption)
|
||||
*/
|
||||
@Throws(DecryptionFailedException::class)
|
||||
fun decrypt(k: ByteArray): InputStream {
|
||||
// 1. The private key used to decrypt is called k.
|
||||
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
|
||||
val P = cryptography().multiply(R, k)
|
||||
// 3. Use the X component of public key P and calculate the SHA512 hash H.
|
||||
val H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33))
|
||||
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||
val key_e = Arrays.copyOfRange(H, 0, 32)
|
||||
val key_m = Arrays.copyOfRange(H, 32, 64)
|
||||
|
||||
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
|
||||
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
|
||||
if (!Arrays.equals(mac, calculateMac(key_m))) {
|
||||
throw DecryptionFailedException()
|
||||
}
|
||||
|
||||
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
|
||||
// and the cipher text as payload. The output is the padded input text.
|
||||
return ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector))
|
||||
}
|
||||
|
||||
private fun calculateMac(key_m: ByteArray): ByteArray {
|
||||
val macData = ByteArrayOutputStream()
|
||||
writeWithoutMAC(macData)
|
||||
return cryptography().mac(key_m, macData.toByteArray())
|
||||
}
|
||||
|
||||
private fun writeWithoutMAC(out: OutputStream) {
|
||||
out.write(initializationVector)
|
||||
Encode.int16(curveType, out)
|
||||
writeCoordinateComponent(out, Points.getX(R))
|
||||
writeCoordinateComponent(out, Points.getY(R))
|
||||
out.write(encrypted)
|
||||
}
|
||||
|
||||
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
|
||||
val offset = Bytes.numberOfLeadingZeros(x)
|
||||
val length = x.size - offset
|
||||
Encode.int16(length, out)
|
||||
out.write(x, offset, length)
|
||||
}
|
||||
|
||||
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
|
||||
val offset = Bytes.numberOfLeadingZeros(x)
|
||||
val length = x.size - offset
|
||||
Encode.int16(length, buffer)
|
||||
buffer.put(x, offset, length)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
writeWithoutMAC(out)
|
||||
out.write(mac)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(initializationVector)
|
||||
Encode.int16(curveType, buffer)
|
||||
writeCoordinateComponent(buffer, Points.getX(R))
|
||||
writeCoordinateComponent(buffer, Points.getY(R))
|
||||
buffer.put(encrypted)
|
||||
buffer.put(mac)
|
||||
}
|
||||
|
||||
class Builder {
|
||||
internal var initializationVector: ByteArray? = null
|
||||
internal var curveType: Int = 0
|
||||
internal var xComponent: ByteArray? = null
|
||||
internal var yComponent: ByteArray? = null
|
||||
internal var encrypted: ByteArray? = null
|
||||
internal var mac: ByteArray? = null
|
||||
|
||||
fun IV(initializationVector: ByteArray): Builder {
|
||||
this.initializationVector = initializationVector
|
||||
return this
|
||||
}
|
||||
|
||||
fun curveType(curveType: Int): Builder {
|
||||
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType)
|
||||
this.curveType = curveType
|
||||
return this
|
||||
}
|
||||
|
||||
fun X(xComponent: ByteArray): Builder {
|
||||
this.xComponent = xComponent
|
||||
return this
|
||||
}
|
||||
|
||||
fun Y(yComponent: ByteArray): Builder {
|
||||
this.yComponent = yComponent
|
||||
return this
|
||||
}
|
||||
|
||||
fun encrypted(encrypted: ByteArray): Builder {
|
||||
this.encrypted = encrypted
|
||||
return this
|
||||
}
|
||||
|
||||
fun MAC(mac: ByteArray): Builder {
|
||||
this.mac = mac
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): CryptoBox {
|
||||
return CryptoBox(this)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(CryptoBox::class.java)
|
||||
|
||||
@JvmStatic fun read(stream: InputStream, length: Int): CryptoBox {
|
||||
val counter = AccessCounter()
|
||||
return Builder()
|
||||
.IV(Decode.bytes(stream, 16, counter))
|
||||
.curveType(Decode.uint16(stream, counter))
|
||||
.X(Decode.shortVarBytes(stream, counter))
|
||||
.Y(Decode.shortVarBytes(stream, counter))
|
||||
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
|
||||
.MAC(Decode.bytes(stream, 32))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
|
||||
* have to know what it is.
|
||||
*/
|
||||
class GenericPayload(version: Long, override val stream: Long, val data: ByteArray) : ObjectPayload(version) {
|
||||
|
||||
override val type: ObjectType? = null
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(data)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(data)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is GenericPayload) return false
|
||||
|
||||
if (stream != other.stream) return false
|
||||
return Arrays.equals(data, other.data)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = (stream xor stream.ushr(32)).toInt()
|
||||
result = 31 * result + Arrays.hashCode(data)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload {
|
||||
return GenericPayload(version, stream, Decode.bytes(`is`, length))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* Request for a public key.
|
||||
*/
|
||||
class GetPubkey : ObjectPayload {
|
||||
|
||||
override val type: ObjectType = ObjectType.GET_PUBKEY
|
||||
override val stream: Long
|
||||
|
||||
/**
|
||||
* @return an array of bytes that represent either the ripe, or the tag of an address, depending on the
|
||||
* * address version.
|
||||
*/
|
||||
val ripeTag: ByteArray
|
||||
|
||||
constructor(address: BitmessageAddress) : super(address.version) {
|
||||
this.stream = address.stream
|
||||
this.ripeTag = if (address.version < 4) address.ripe else
|
||||
address.tag ?: throw IllegalStateException("Address of version 4 without tag shouldn't exist!")
|
||||
}
|
||||
|
||||
private constructor(version: Long, stream: Long, ripeOrTag: ByteArray) : super(version) {
|
||||
this.stream = stream
|
||||
this.ripeTag = ripeOrTag
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(ripeTag)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(ripeTag)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
|
||||
return GetPubkey(version, stream, Decode.bytes(`is`, length))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.Encrypted
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||
import ch.dissem.bitmessage.entity.PlaintextHolder
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* Used for person-to-person messages.
|
||||
*/
|
||||
class Msg : ObjectPayload, Encrypted, PlaintextHolder {
|
||||
|
||||
override val stream: Long
|
||||
private var encrypted: CryptoBox?
|
||||
override var plaintext: Plaintext?
|
||||
private set
|
||||
|
||||
private constructor(stream: Long, encrypted: CryptoBox) : super(1) {
|
||||
this.stream = stream
|
||||
this.encrypted = encrypted
|
||||
this.plaintext = null
|
||||
}
|
||||
|
||||
constructor(plaintext: Plaintext) : super(1) {
|
||||
this.stream = plaintext.stream
|
||||
this.encrypted = null
|
||||
this.plaintext = plaintext
|
||||
}
|
||||
|
||||
override val type: ObjectType = ObjectType.MSG
|
||||
|
||||
override val isSigned: Boolean = true
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
|
||||
override var signature: ByteArray?
|
||||
get() = plaintext?.signature
|
||||
set(signature) {
|
||||
plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
|
||||
override fun encrypt(publicKey: ByteArray) {
|
||||
this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey)
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
override fun decrypt(privateKey: ByteArray) {
|
||||
plaintext = Plaintext.read(MSG, encrypted!!.decrypt(privateKey))
|
||||
}
|
||||
|
||||
override val isDecrypted: Boolean
|
||||
get() = plaintext != null
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Msg) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return stream.toInt()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ACK_LENGTH = 32
|
||||
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg {
|
||||
return Msg(stream, CryptoBox.read(`in`, length))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* The payload of an 'object' command. This is shared by the network.
|
||||
*/
|
||||
abstract class ObjectPayload protected constructor(val version: Long) : Streamable {
|
||||
|
||||
abstract val type: ObjectType?
|
||||
|
||||
abstract val stream: Long
|
||||
|
||||
open val isSigned: Boolean = false
|
||||
|
||||
open fun writeBytesToSign(out: OutputStream) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
|
||||
* * appended with the data described in this table down to the extra_bytes. Therefore, this must
|
||||
* * be checked and set in the [ObjectMessage] object.
|
||||
*/
|
||||
open var signature: ByteArray? = null
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
/**
|
||||
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
|
||||
*/
|
||||
enum class ObjectType constructor(val number: Long) {
|
||||
GET_PUBKEY(0),
|
||||
PUBKEY(1),
|
||||
MSG(2),
|
||||
BROADCAST(3);
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun fromNumber(number: Long): ObjectType? {
|
||||
return values().firstOrNull { it.number == number }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
||||
*/
|
||||
abstract class Pubkey protected constructor(version: Long) : ObjectPayload(version) {
|
||||
|
||||
override val type: ObjectType = ObjectType.PUBKEY
|
||||
|
||||
abstract val signingKey: ByteArray
|
||||
|
||||
abstract val encryptionKey: ByteArray
|
||||
|
||||
abstract val behaviorBitfield: Int
|
||||
|
||||
val ripe: ByteArray by lazy { cryptography().ripemd160(cryptography().sha512(signingKey, encryptionKey)) }
|
||||
|
||||
open val nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
|
||||
open val extraBytes: Long = NETWORK_EXTRA_BYTES
|
||||
|
||||
open fun writeUnencrypted(out: OutputStream) {
|
||||
write(out)
|
||||
}
|
||||
|
||||
open fun writeUnencrypted(buffer: ByteBuffer) {
|
||||
write(buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bits 0 through 29 are yet undefined
|
||||
*/
|
||||
enum class Feature constructor(bitNumber: Int) {
|
||||
/**
|
||||
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
|
||||
* messages bound for them.
|
||||
*/
|
||||
INCLUDE_DESTINATION(30),
|
||||
/**
|
||||
* If true, the receiving node does send acknowledgements (rather than dropping them).
|
||||
*/
|
||||
DOES_ACK(31);
|
||||
|
||||
// The Bitmessage Protocol Specification starts counting at the most significant bit,
|
||||
// thus the slightly awkward calculation.
|
||||
// https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features
|
||||
private val bit: Int = 1 shl 31 - bitNumber
|
||||
|
||||
fun isActive(bitfield: Int): Boolean {
|
||||
return bitfield and bit != 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun bitfield(vararg features: Feature): Int {
|
||||
var bits = 0
|
||||
for (feature in features) {
|
||||
bits = bits or feature.bit
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
@JvmStatic fun features(bitfield: Int): Array<Feature> {
|
||||
val features = ArrayList<Feature>(Feature.values().size)
|
||||
for (feature in Feature.values()) {
|
||||
if (bitfield and feature.bit != 0) {
|
||||
features.add(feature)
|
||||
}
|
||||
}
|
||||
return features.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val LATEST_VERSION: Long = 4
|
||||
|
||||
fun getRipe(publicSigningKey: ByteArray, publicEncryptionKey: ByteArray): ByteArray {
|
||||
return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey))
|
||||
}
|
||||
|
||||
fun add0x04(key: ByteArray): ByteArray {
|
||||
if (key.size == 65) return key
|
||||
val result = ByteArray(65)
|
||||
result[0] = 4
|
||||
System.arraycopy(key, 0, result, 1, 64)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* A version 2 public key.
|
||||
*/
|
||||
open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) {
|
||||
|
||||
override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey
|
||||
override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.int32(behaviorBitfield, out)
|
||||
out.write(signingKey, 1, 64)
|
||||
out.write(encryptionKey, 1, 64)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.int32(behaviorBitfield, buffer)
|
||||
buffer.put(signingKey, 1, 64)
|
||||
buffer.put(encryptionKey, 1, 64)
|
||||
}
|
||||
|
||||
class Builder {
|
||||
internal var streamNumber: Long = 0
|
||||
internal var behaviorBitfield: Int = 0
|
||||
internal var publicSigningKey: ByteArray? = null
|
||||
internal var publicEncryptionKey: ByteArray? = null
|
||||
|
||||
fun stream(streamNumber: Long): Builder {
|
||||
this.streamNumber = streamNumber
|
||||
return this
|
||||
}
|
||||
|
||||
fun behaviorBitfield(behaviorBitfield: Int): Builder {
|
||||
this.behaviorBitfield = behaviorBitfield
|
||||
return this
|
||||
}
|
||||
|
||||
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
|
||||
this.publicSigningKey = publicSigningKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
|
||||
this.publicEncryptionKey = publicEncryptionKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): V2Pubkey {
|
||||
return V2Pubkey(
|
||||
version = 2,
|
||||
stream = streamNumber,
|
||||
behaviorBitfield = behaviorBitfield,
|
||||
signingKey = add0x04(publicSigningKey!!),
|
||||
encryptionKey = add0x04(publicEncryptionKey!!)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey {
|
||||
return V2Pubkey(
|
||||
version = 2,
|
||||
stream = stream,
|
||||
behaviorBitfield = Decode.uint32(`in`).toInt(),
|
||||
signingKey = Decode.bytes(`in`, 64),
|
||||
encryptionKey = Decode.bytes(`in`, 64)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A version 3 public key.
|
||||
*/
|
||||
class V3Pubkey protected constructor(
|
||||
version: Long, stream: Long, behaviorBitfield: Int,
|
||||
signingKey: ByteArray, encryptionKey: ByteArray,
|
||||
override val nonceTrialsPerByte: Long,
|
||||
override val extraBytes: Long,
|
||||
override var signature: ByteArray? = null
|
||||
) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) {
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
writeBytesToSign(out)
|
||||
Encode.varBytes(
|
||||
signature ?: throw IllegalStateException("signature not available"),
|
||||
out
|
||||
)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
super.write(buffer)
|
||||
Encode.varInt(nonceTrialsPerByte, buffer)
|
||||
Encode.varInt(extraBytes, buffer)
|
||||
Encode.varBytes(
|
||||
signature ?: throw IllegalStateException("signature not available"),
|
||||
buffer
|
||||
)
|
||||
}
|
||||
|
||||
override val isSigned: Boolean = true
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
super.write(out)
|
||||
Encode.varInt(nonceTrialsPerByte, out)
|
||||
Encode.varInt(extraBytes, out)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is V3Pubkey) return false
|
||||
return nonceTrialsPerByte == other.nonceTrialsPerByte &&
|
||||
extraBytes == other.extraBytes &&
|
||||
stream == other.stream &&
|
||||
behaviorBitfield == other.behaviorBitfield &&
|
||||
Arrays.equals(signingKey, other.signingKey) &&
|
||||
Arrays.equals(encryptionKey, other.encryptionKey)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(nonceTrialsPerByte, extraBytes)
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var streamNumber: Long = 0
|
||||
private var behaviorBitfield: Int = 0
|
||||
private var publicSigningKey: ByteArray? = null
|
||||
private var publicEncryptionKey: ByteArray? = null
|
||||
private var nonceTrialsPerByte: Long = 0
|
||||
private var extraBytes: Long = 0
|
||||
private var signature = ByteArray(0)
|
||||
|
||||
fun stream(streamNumber: Long): Builder {
|
||||
this.streamNumber = streamNumber
|
||||
return this
|
||||
}
|
||||
|
||||
fun behaviorBitfield(behaviorBitfield: Int): Builder {
|
||||
this.behaviorBitfield = behaviorBitfield
|
||||
return this
|
||||
}
|
||||
|
||||
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
|
||||
this.publicSigningKey = publicSigningKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
|
||||
this.publicEncryptionKey = publicEncryptionKey
|
||||
return this
|
||||
}
|
||||
|
||||
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
|
||||
this.nonceTrialsPerByte = nonceTrialsPerByte
|
||||
return this
|
||||
}
|
||||
|
||||
fun extraBytes(extraBytes: Long): Builder {
|
||||
this.extraBytes = extraBytes
|
||||
return this
|
||||
}
|
||||
|
||||
fun signature(signature: ByteArray): Builder {
|
||||
this.signature = signature
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): V3Pubkey {
|
||||
return V3Pubkey(
|
||||
version = 3,
|
||||
stream = streamNumber,
|
||||
behaviorBitfield = behaviorBitfield,
|
||||
signingKey = publicSigningKey!!,
|
||||
encryptionKey = publicEncryptionKey!!,
|
||||
nonceTrialsPerByte = nonceTrialsPerByte,
|
||||
extraBytes = extraBytes,
|
||||
signature = signature
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey {
|
||||
return V3Pubkey(
|
||||
version = 3,
|
||||
stream = stream,
|
||||
behaviorBitfield = Decode.int32(`is`),
|
||||
signingKey = Decode.bytes(`is`, 64),
|
||||
encryptionKey = Decode.bytes(`is`, 64),
|
||||
nonceTrialsPerByte = Decode.varInt(`is`),
|
||||
extraBytes = Decode.varInt(`is`),
|
||||
signature = Decode.varBytes(`is`)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
* Broadcasts are version 4 or 5.
|
||||
*/
|
||||
open class V4Broadcast : Broadcast {
|
||||
|
||||
override val type: ObjectType = ObjectType.BROADCAST
|
||||
|
||||
protected constructor(version: Long, stream: Long, encrypted: CryptoBox?, plaintext: Plaintext?) : super(version, stream, encrypted, plaintext)
|
||||
|
||||
constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(4, senderAddress.stream, null, plaintext) {
|
||||
if (senderAddress.version >= 4)
|
||||
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
|
||||
}
|
||||
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast {
|
||||
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Encrypted
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
|
||||
* done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and
|
||||
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
|
||||
* to create messages to be used in spam or in flooding attacks.
|
||||
*/
|
||||
class V4Pubkey : Pubkey, Encrypted {
|
||||
|
||||
override val stream: Long
|
||||
val tag: ByteArray
|
||||
private var encrypted: CryptoBox? = null
|
||||
private var decrypted: V3Pubkey? = null
|
||||
|
||||
private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(4) {
|
||||
this.stream = stream
|
||||
this.tag = tag
|
||||
this.encrypted = encrypted
|
||||
}
|
||||
|
||||
constructor(decrypted: V3Pubkey) : super(4) {
|
||||
this.stream = decrypted.stream
|
||||
this.decrypted = decrypted
|
||||
this.tag = BitmessageAddress.calculateTag(4, decrypted.stream, decrypted.ripe)
|
||||
}
|
||||
|
||||
override fun encrypt(publicKey: ByteArray) {
|
||||
if (signature == null) throw IllegalStateException("Pubkey must be signed before encryption.")
|
||||
this.encrypted = CryptoBox(decrypted ?: throw IllegalStateException("no plaintext pubkey data available"), publicKey)
|
||||
}
|
||||
|
||||
@Throws(DecryptionFailedException::class)
|
||||
override fun decrypt(privateKey: ByteArray) {
|
||||
decrypted = V3Pubkey.read(encrypted?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"), stream)
|
||||
}
|
||||
|
||||
override val isDecrypted: Boolean
|
||||
get() = decrypted != null
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(tag)
|
||||
encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(tag)
|
||||
encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(out: OutputStream) {
|
||||
decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeUnencrypted(buffer: ByteBuffer) {
|
||||
decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
out.write(tag)
|
||||
decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
|
||||
}
|
||||
|
||||
override val signingKey: ByteArray
|
||||
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
|
||||
|
||||
override val encryptionKey: ByteArray
|
||||
get() = decrypted?.encryptionKey ?: throw IllegalStateException("pubkey is encrypted")
|
||||
|
||||
override val behaviorBitfield: Int
|
||||
get() = decrypted?.behaviorBitfield ?: throw IllegalStateException("pubkey is encrypted")
|
||||
|
||||
override var signature: ByteArray?
|
||||
get() = decrypted?.signature
|
||||
set(signature) {
|
||||
decrypted?.signature = signature
|
||||
}
|
||||
|
||||
override val isSigned: Boolean = true
|
||||
|
||||
override val nonceTrialsPerByte: Long
|
||||
get() = decrypted?.nonceTrialsPerByte ?: throw IllegalStateException("pubkey is encrypted")
|
||||
|
||||
override val extraBytes: Long
|
||||
get() = decrypted?.extraBytes ?: throw IllegalStateException("pubkey is encrypted")
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is V4Pubkey) return false
|
||||
|
||||
if (stream != other.stream) return false
|
||||
if (!Arrays.equals(tag, other.tag)) return false
|
||||
return !if (decrypted != null) decrypted != other.decrypted else other.decrypted != null
|
||||
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = (stream xor stream.ushr(32)).toInt()
|
||||
result = 31 * result + Arrays.hashCode(tag)
|
||||
result = 31 * result + if (decrypted != null) decrypted!!.hashCode() else 0
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey {
|
||||
if (encrypted)
|
||||
return V4Pubkey(stream,
|
||||
Decode.bytes(`in`, 32),
|
||||
CryptoBox.read(`in`, length - 32))
|
||||
else
|
||||
return V4Pubkey(V3Pubkey.read(`in`, stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.payload
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
*/
|
||||
class V5Broadcast : V4Broadcast {
|
||||
|
||||
val tag: ByteArray
|
||||
|
||||
private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(5, stream, encrypted, null) {
|
||||
this.tag = tag
|
||||
}
|
||||
|
||||
constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(5, senderAddress.stream, null, plaintext) {
|
||||
if (senderAddress.version < 4)
|
||||
throw IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.version)
|
||||
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
|
||||
}
|
||||
|
||||
override fun writeBytesToSign(out: OutputStream) {
|
||||
out.write(tag)
|
||||
super.writeBytesToSign(out)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(tag)
|
||||
super.write(out)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast {
|
||||
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject
|
||||
|
||||
import ch.dissem.msgpack.types.MPMap
|
||||
import ch.dissem.msgpack.types.MPString
|
||||
import ch.dissem.msgpack.types.MPType
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.Serializable
|
||||
import java.util.zip.DeflaterOutputStream
|
||||
|
||||
/**
|
||||
* Extended encoding message object.
|
||||
*/
|
||||
data class ExtendedEncoding(val content: ExtendedEncoding.ExtendedType) : Serializable {
|
||||
|
||||
val type: String? = content.type
|
||||
|
||||
fun zip(): ByteArray {
|
||||
ByteArrayOutputStream().use { out ->
|
||||
DeflaterOutputStream(out).use { zipper -> content.pack().pack(zipper) }
|
||||
return out.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
interface Unpacker<out T : ExtendedType> {
|
||||
val type: String
|
||||
|
||||
fun unpack(map: MPMap<MPString, MPType<*>>): T
|
||||
}
|
||||
|
||||
interface ExtendedType : Serializable {
|
||||
val type: String
|
||||
|
||||
fun pack(): MPMap<MPString, MPType<*>>
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
data class InventoryVector constructor(
|
||||
/**
|
||||
* Hash of the object
|
||||
*/
|
||||
val hash: ByteArray) : Streamable {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is InventoryVector) return false
|
||||
|
||||
return Arrays.equals(hash, other.hash)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Arrays.hashCode(hash)
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
out.write(hash)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
buffer.put(hash)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return Strings.hex(hash)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? {
|
||||
return InventoryVector(
|
||||
hash ?: return null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
data class Label(
|
||||
private val label: String,
|
||||
val type: Label.Type? = null,
|
||||
/**
|
||||
* RGBA representation for the color.
|
||||
*/
|
||||
var color: Int = 0,
|
||||
var ord: Int = 1000
|
||||
) : Serializable {
|
||||
|
||||
var id: Any? = null
|
||||
|
||||
override fun toString(): String {
|
||||
return label
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Label) return false
|
||||
return label == other.label
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(label)
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
INBOX,
|
||||
BROADCAST,
|
||||
DRAFT,
|
||||
OUTBOX,
|
||||
SENT,
|
||||
UNREAD,
|
||||
TRASH
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.Version
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import java.io.OutputStream
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.net.SocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
fun ip6(inetAddress: InetAddress): ByteArray {
|
||||
val address = inetAddress.address
|
||||
when (address.size) {
|
||||
16 -> {
|
||||
return address
|
||||
}
|
||||
4 -> {
|
||||
val ip6 = ByteArray(16)
|
||||
ip6[10] = 0xff.toByte()
|
||||
ip6[11] = 0xff.toByte()
|
||||
System.arraycopy(address, 0, ip6, 12, 4)
|
||||
return ip6
|
||||
}
|
||||
else -> throw IllegalArgumentException("Weird address " + inetAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node's address. It's written in IPv6 format.
|
||||
*/
|
||||
data class NetworkAddress(
|
||||
var time: Long,
|
||||
|
||||
/**
|
||||
* Stream number for this node
|
||||
*/
|
||||
val stream: Long,
|
||||
|
||||
/**
|
||||
* same service(s) listed in version
|
||||
*/
|
||||
val services: Long,
|
||||
|
||||
/**
|
||||
* IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address
|
||||
* (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address).
|
||||
*/
|
||||
val IPv6: ByteArray,
|
||||
val port: Int
|
||||
) : Streamable {
|
||||
|
||||
constructor(time: Long, stream: Long, services: Long = 1, socket: Socket)
|
||||
: this(time, stream, services, ip6(socket.inetAddress), socket.port)
|
||||
|
||||
constructor(time: Long, stream: Long, services: Long = 1, inetAddress: InetAddress, port: Int)
|
||||
: this(time, stream, services, ip6(inetAddress), port)
|
||||
|
||||
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
|
||||
|
||||
fun toInetAddress(): InetAddress {
|
||||
return InetAddress.getByAddress(IPv6)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is NetworkAddress) return false
|
||||
|
||||
return port == other.port && Arrays.equals(IPv6, other.IPv6)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = Arrays.hashCode(IPv6)
|
||||
result = 31 * result + port
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "[" + toInetAddress() + "]:" + port
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
write(out, false)
|
||||
}
|
||||
|
||||
fun write(out: OutputStream, light: Boolean) {
|
||||
if (!light) {
|
||||
Encode.int64(time, out)
|
||||
Encode.int32(stream, out)
|
||||
}
|
||||
Encode.int64(services, out)
|
||||
out.write(IPv6)
|
||||
Encode.int16(port, out)
|
||||
}
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
write(buffer, false)
|
||||
}
|
||||
|
||||
fun write(buffer: ByteBuffer, light: Boolean) {
|
||||
if (!light) {
|
||||
Encode.int64(time, buffer)
|
||||
Encode.int32(stream, buffer)
|
||||
}
|
||||
Encode.int64(services, buffer)
|
||||
buffer.put(IPv6)
|
||||
Encode.int16(port, buffer)
|
||||
}
|
||||
|
||||
class Builder {
|
||||
internal var time: Long? = null
|
||||
internal var stream: Long = 0
|
||||
internal var services: Long = 1
|
||||
internal var ipv6: ByteArray? = null
|
||||
internal var port: Int = 0
|
||||
|
||||
fun time(time: Long): Builder {
|
||||
this.time = time
|
||||
return this
|
||||
}
|
||||
|
||||
fun stream(stream: Long): Builder {
|
||||
this.stream = stream
|
||||
return this
|
||||
}
|
||||
|
||||
fun services(services: Long): Builder {
|
||||
this.services = services
|
||||
return this
|
||||
}
|
||||
|
||||
fun ip(inetAddress: InetAddress): Builder {
|
||||
ipv6 = ip6(inetAddress)
|
||||
return this
|
||||
}
|
||||
|
||||
fun ipv6(ipv6: ByteArray): Builder {
|
||||
this.ipv6 = ipv6
|
||||
return this
|
||||
}
|
||||
|
||||
fun ipv6(p00: Int, p01: Int, p02: Int, p03: Int,
|
||||
p04: Int, p05: Int, p06: Int, p07: Int,
|
||||
p08: Int, p09: Int, p10: Int, p11: Int,
|
||||
p12: Int, p13: Int, p14: Int, p15: Int): Builder {
|
||||
this.ipv6 = byteArrayOf(p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte(), p04.toByte(), p05.toByte(), p06.toByte(), p07.toByte(), p08.toByte(), p09.toByte(), p10.toByte(), p11.toByte(), p12.toByte(), p13.toByte(), p14.toByte(), p15.toByte())
|
||||
return this
|
||||
}
|
||||
|
||||
fun ipv4(p00: Int, p01: Int, p02: Int, p03: Int): Builder {
|
||||
this.ipv6 = byteArrayOf(0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0xff.toByte(), 0xff.toByte(), p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte())
|
||||
return this
|
||||
}
|
||||
|
||||
fun port(port: Int): Builder {
|
||||
this.port = port
|
||||
return this
|
||||
}
|
||||
|
||||
fun address(address: SocketAddress): Builder {
|
||||
if (address is InetSocketAddress) {
|
||||
ip(address.address)
|
||||
port(address.port)
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown type of address: " + address.javaClass)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): NetworkAddress {
|
||||
return NetworkAddress(
|
||||
time ?: UnixTime.now, stream, services, ipv6!!, port
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0)
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Streamable
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.factory.Factory
|
||||
import ch.dissem.bitmessage.utils.Bytes
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import ch.dissem.bitmessage.utils.Encode
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
|
||||
* [Pubkey] object.
|
||||
*/
|
||||
data class PrivateKey(
|
||||
val privateSigningKey: ByteArray,
|
||||
val privateEncryptionKey: ByteArray,
|
||||
|
||||
val pubkey: Pubkey
|
||||
) : Streamable {
|
||||
|
||||
constructor(
|
||||
shorter: Boolean,
|
||||
stream: Long,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long,
|
||||
vararg features: Pubkey.Feature
|
||||
) : this(
|
||||
Builder(version = Pubkey.LATEST_VERSION, stream = stream, shorter = shorter)
|
||||
.random()
|
||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||
.extraBytes(extraBytes)
|
||||
.features(features)
|
||||
.generate())
|
||||
|
||||
constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase)
|
||||
|
||||
constructor(version: Long, stream: Long, passphrase: String) : this(
|
||||
Builder(version, stream, false).seed(passphrase).generate()
|
||||
)
|
||||
|
||||
private constructor(builder: Builder) : this(
|
||||
builder.privSK!!, builder.privEK!!,
|
||||
Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!,
|
||||
builder.nonceTrialsPerByte, builder.extraBytes, *builder.features)
|
||||
)
|
||||
|
||||
private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) {
|
||||
|
||||
internal var seed: ByteArray? = null
|
||||
internal var nextNonce: Long = 0
|
||||
|
||||
internal var privSK: ByteArray? = null
|
||||
internal var privEK: ByteArray? = null
|
||||
internal var pubSK: ByteArray? = null
|
||||
internal var pubEK: ByteArray? = null
|
||||
|
||||
internal var nonceTrialsPerByte = InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
internal var extraBytes = InternalContext.NETWORK_EXTRA_BYTES
|
||||
internal var features: Array<out Pubkey.Feature> = emptyArray()
|
||||
|
||||
internal fun random(): Builder {
|
||||
seed = cryptography().randomBytes(1024)
|
||||
return this
|
||||
}
|
||||
|
||||
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
|
||||
this.nonceTrialsPerByte = nonceTrialsPerByte
|
||||
return this
|
||||
}
|
||||
|
||||
fun extraBytes(extraBytes: Long): Builder {
|
||||
this.extraBytes = extraBytes
|
||||
return this
|
||||
}
|
||||
|
||||
fun features(features: Array<out Pubkey.Feature>): Builder {
|
||||
this.features = features
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun seed(passphrase: String): Builder {
|
||||
try {
|
||||
seed = passphrase.toByteArray(charset("UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun generate(): Builder {
|
||||
var signingKeyNonce = nextNonce
|
||||
var encryptionKeyNonce = nextNonce + 1
|
||||
var ripe: ByteArray
|
||||
do {
|
||||
privEK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(encryptionKeyNonce)), 32)
|
||||
privSK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(signingKeyNonce)), 32)
|
||||
pubSK = cryptography().createPublicKey(privSK!!)
|
||||
pubEK = cryptography().createPublicKey(privEK!!)
|
||||
ripe = cryptography().ripemd160(cryptography().sha512(pubSK!!, pubEK!!))
|
||||
|
||||
signingKeyNonce += 2
|
||||
encryptionKeyNonce += 2
|
||||
} while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0)
|
||||
nextNonce = signingKeyNonce
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(out: OutputStream) {
|
||||
Encode.varInt(pubkey.version, out)
|
||||
Encode.varInt(pubkey.stream, out)
|
||||
val baos = ByteArrayOutputStream()
|
||||
pubkey.writeUnencrypted(baos)
|
||||
Encode.varInt(baos.size(), out)
|
||||
out.write(baos.toByteArray())
|
||||
Encode.varBytes(privateSigningKey, out)
|
||||
Encode.varBytes(privateEncryptionKey, out)
|
||||
}
|
||||
|
||||
|
||||
override fun write(buffer: ByteBuffer) {
|
||||
Encode.varInt(pubkey.version, buffer)
|
||||
Encode.varInt(pubkey.stream, buffer)
|
||||
try {
|
||||
val baos = ByteArrayOutputStream()
|
||||
pubkey.writeUnencrypted(baos)
|
||||
Encode.varBytes(baos.toByteArray(), buffer)
|
||||
} catch (e: IOException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
Encode.varBytes(privateSigningKey, buffer)
|
||||
Encode.varBytes(privateEncryptionKey, buffer)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = other is PrivateKey
|
||||
&& Arrays.equals(privateEncryptionKey, other.privateEncryptionKey)
|
||||
&& Arrays.equals(privateSigningKey, other.privateSigningKey)
|
||||
&& pubkey == other.pubkey
|
||||
|
||||
override fun hashCode() = pubkey.hashCode()
|
||||
|
||||
companion object {
|
||||
@JvmField val PRIVATE_KEY_SIZE = 32
|
||||
|
||||
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
|
||||
val result = ArrayList<PrivateKey>(numberOfAddresses)
|
||||
val builder = Builder(version, stream, shorter).seed(passphrase)
|
||||
for (i in 0..numberOfAddresses - 1) {
|
||||
builder.generate()
|
||||
result.add(PrivateKey(builder))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic fun read(`is`: InputStream): PrivateKey {
|
||||
val version = Decode.varInt(`is`).toInt()
|
||||
val stream = Decode.varInt(`is`)
|
||||
val len = Decode.varInt(`is`).toInt()
|
||||
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
|
||||
val signingKey = Decode.varBytes(`is`)
|
||||
val encryptionKey = Decode.varBytes(`is`)
|
||||
return PrivateKey(signingKey, encryptionKey, pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject.extended
|
||||
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A "file" attachment as used by extended encoding type messages. Could either be an attachment,
|
||||
* or used inline to be used by a HTML message, for example.
|
||||
*/
|
||||
data class Attachment constructor(
|
||||
val name: String,
|
||||
val data: ByteArray,
|
||||
val type: String,
|
||||
val disposition: Disposition
|
||||
) : Serializable {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Attachment) return false
|
||||
return name == other.name &&
|
||||
Arrays.equals(data, other.data) &&
|
||||
type == other.type &&
|
||||
disposition == other.disposition
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(name, data, type, disposition)
|
||||
}
|
||||
|
||||
enum class Disposition {
|
||||
inline, attachment
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var name: String? = null
|
||||
private var data: ByteArray? = null
|
||||
private var type: String? = null
|
||||
private var disposition: Disposition? = null
|
||||
|
||||
fun name(name: String): Builder {
|
||||
this.name = name
|
||||
return this
|
||||
}
|
||||
|
||||
fun data(data: ByteArray): Builder {
|
||||
this.data = data
|
||||
return this
|
||||
}
|
||||
|
||||
fun type(type: String): Builder {
|
||||
this.type = type
|
||||
return this
|
||||
}
|
||||
|
||||
fun inline(): Builder {
|
||||
this.disposition = Disposition.inline
|
||||
return this
|
||||
}
|
||||
|
||||
fun attachment(): Builder {
|
||||
this.disposition = Disposition.attachment
|
||||
return this
|
||||
}
|
||||
|
||||
fun disposition(disposition: Disposition): Builder {
|
||||
this.disposition = disposition
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): Attachment {
|
||||
return Attachment(name!!, data!!, type!!, disposition!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject.extended
|
||||
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.utils.Strings.str
|
||||
import ch.dissem.msgpack.types.*
|
||||
import ch.dissem.msgpack.types.Utils.mp
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.IOException
|
||||
import java.net.URLConnection
|
||||
import java.nio.file.Files
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work
|
||||
* properly with future PyBitmessage implementations.
|
||||
*/
|
||||
data class Message constructor(
|
||||
val subject: String,
|
||||
val body: String,
|
||||
val parents: List<InventoryVector>,
|
||||
val files: List<Attachment>
|
||||
) : ExtendedEncoding.ExtendedType {
|
||||
|
||||
override val type: String = TYPE
|
||||
|
||||
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||
val result = MPMap<MPString, MPType<*>>()
|
||||
result.put(mp(""), mp(TYPE))
|
||||
result.put(mp("subject"), mp(subject))
|
||||
result.put(mp("body"), mp(body))
|
||||
|
||||
if (!files.isEmpty()) {
|
||||
val items = MPArray<MPMap<MPString, MPType<*>>>()
|
||||
result.put(mp("files"), items)
|
||||
for (file in files) {
|
||||
val item = MPMap<MPString, MPType<*>>()
|
||||
item.put(mp("name"), mp(file.name))
|
||||
item.put(mp("data"), mp(*file.data))
|
||||
item.put(mp("type"), mp(file.type))
|
||||
item.put(mp("disposition"), mp(file.disposition.name))
|
||||
items.add(item)
|
||||
}
|
||||
}
|
||||
if (!parents.isEmpty()) {
|
||||
val items = MPArray<MPBinary>()
|
||||
result.put(mp("parents"), items)
|
||||
for ((hash) in parents) {
|
||||
items.add(mp(*hash))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var subject: String? = null
|
||||
private var body: String? = null
|
||||
private val parents = LinkedList<InventoryVector>()
|
||||
private val files = LinkedList<Attachment>()
|
||||
|
||||
fun subject(subject: String): Builder {
|
||||
this.subject = subject
|
||||
return this
|
||||
}
|
||||
|
||||
fun body(body: String): Builder {
|
||||
this.body = body
|
||||
return this
|
||||
}
|
||||
|
||||
fun addParent(parent: Plaintext?): Builder {
|
||||
if (parent != null) {
|
||||
val iv = parent.inventoryVector
|
||||
if (iv == null) {
|
||||
LOG.debug("Ignored parent without IV")
|
||||
} else {
|
||||
parents.add(iv)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun addParent(iv: InventoryVector?): Builder {
|
||||
if (iv != null) {
|
||||
parents.add(iv)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun addFile(file: File?, disposition: Attachment.Disposition): Builder {
|
||||
if (file != null) {
|
||||
try {
|
||||
files.add(Attachment.Builder()
|
||||
.name(file.name)
|
||||
.disposition(disposition)
|
||||
.type(URLConnection.guessContentTypeFromStream(FileInputStream(file)))
|
||||
.data(Files.readAllBytes(file.toPath()))
|
||||
.build())
|
||||
} catch (e: IOException) {
|
||||
LOG.error(e.message, e)
|
||||
}
|
||||
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun addFile(file: Attachment?): Builder {
|
||||
if (file != null) {
|
||||
files.add(file)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): ExtendedEncoding {
|
||||
return ExtendedEncoding(Message(subject!!, body!!, parents, files))
|
||||
}
|
||||
}
|
||||
|
||||
class Unpacker : ExtendedEncoding.Unpacker<Message> {
|
||||
override val type: String = TYPE
|
||||
|
||||
override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
|
||||
val subject = str(map[mp("subject")]) ?: ""
|
||||
val body = str(map[mp("body")]) ?: ""
|
||||
val parents = LinkedList<InventoryVector>()
|
||||
val files = LinkedList<Attachment>()
|
||||
val mpParents = map[mp("parents")] as? MPArray<*>
|
||||
for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) {
|
||||
parents.add(InventoryVector.fromHash(
|
||||
(parent as? MPBinary)?.value ?: continue
|
||||
) ?: continue)
|
||||
}
|
||||
val mpFiles = map[mp("files")] as? MPArray<*>
|
||||
for (item in mpFiles ?: emptyList<Any>()) {
|
||||
if (item is MPMap<*, *>) {
|
||||
val b = Attachment.Builder()
|
||||
b.name(str(item[mp("name")])!!)
|
||||
b.data(
|
||||
bin(item[mp("data")] ?: continue) ?: continue
|
||||
)
|
||||
b.type(str(item[mp("type")])!!)
|
||||
val disposition = str(item[mp("disposition")])
|
||||
if ("inline" == disposition) {
|
||||
b.inline()
|
||||
} else if ("attachment" == disposition) {
|
||||
b.attachment()
|
||||
}
|
||||
files.add(b.build())
|
||||
}
|
||||
}
|
||||
|
||||
return Message(subject, body, parents, files)
|
||||
}
|
||||
|
||||
private fun bin(data: MPType<*>): ByteArray? {
|
||||
return (data as? MPBinary)?.value
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(Message::class.java)
|
||||
|
||||
val TYPE = "message"
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.entity.valueobject.extended
|
||||
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.utils.Strings.str
|
||||
import ch.dissem.msgpack.types.MPBinary
|
||||
import ch.dissem.msgpack.types.MPMap
|
||||
import ch.dissem.msgpack.types.MPString
|
||||
import ch.dissem.msgpack.types.MPType
|
||||
import ch.dissem.msgpack.types.Utils.mp
|
||||
|
||||
/**
|
||||
* Extended encoding type 'vote'. Specification still outstanding, so this will need some work.
|
||||
*/
|
||||
data class Vote constructor(val msgId: InventoryVector, val vote: String) : ExtendedEncoding.ExtendedType {
|
||||
|
||||
override val type: String = TYPE
|
||||
|
||||
override fun pack(): MPMap<MPString, MPType<*>> {
|
||||
val result = MPMap<MPString, MPType<*>>()
|
||||
result.put(mp(""), mp(TYPE))
|
||||
result.put(mp("msgId"), mp(*msgId.hash))
|
||||
result.put(mp("vote"), mp(vote))
|
||||
return result
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private var msgId: InventoryVector? = null
|
||||
private var vote: String? = null
|
||||
|
||||
fun up(message: Plaintext): ExtendedEncoding {
|
||||
msgId = message.inventoryVector
|
||||
vote = "1"
|
||||
return ExtendedEncoding(Vote(msgId!!, vote!!))
|
||||
}
|
||||
|
||||
fun down(message: Plaintext): ExtendedEncoding {
|
||||
msgId = message.inventoryVector
|
||||
vote = "-1"
|
||||
return ExtendedEncoding(Vote(msgId!!, vote!!))
|
||||
}
|
||||
|
||||
fun msgId(iv: InventoryVector): Builder {
|
||||
this.msgId = iv
|
||||
return this
|
||||
}
|
||||
|
||||
fun vote(vote: String): Builder {
|
||||
this.vote = vote
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): ExtendedEncoding {
|
||||
return ExtendedEncoding(Vote(msgId!!, vote!!))
|
||||
}
|
||||
}
|
||||
|
||||
class Unpacker : ExtendedEncoding.Unpacker<Vote> {
|
||||
override val type: String
|
||||
get() = TYPE
|
||||
|
||||
override fun unpack(map: MPMap<MPString, MPType<*>>): Vote {
|
||||
val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
|
||||
val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given")
|
||||
return Vote(msgId, vote)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField val TYPE = "vote"
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.exception
|
||||
|
||||
/**
|
||||
* Indicates an illegal Bitmessage address
|
||||
*/
|
||||
class AddressFormatException(message: String) : RuntimeException(message)
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.exception
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
class ApplicationException : RuntimeException {
|
||||
|
||||
constructor(cause: Throwable) : super(cause)
|
||||
|
||||
constructor(message: String) : super(message)
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.exception
|
||||
|
||||
class DecryptionFailedException : Exception()
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.exception
|
||||
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class InsufficientProofOfWorkException(target: ByteArray, hash: ByteArray) : IOException(
|
||||
"Insufficient proof of work: " + Strings.hex(target) + " required, "
|
||||
+ Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.")
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.exception
|
||||
|
||||
/**
|
||||
* An exception on the node that's severe enough to cause the client to disconnect this node.
|
||||
|
||||
* @author Ch. Basler
|
||||
*/
|
||||
class NodeException(message: String?, cause: Throwable? = null) : RuntimeException(message ?: cause?.message, cause) {
|
||||
constructor(message: String) : this(message, null)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.factory
|
||||
|
||||
import ch.dissem.bitmessage.constants.Network.HEADER_SIZE
|
||||
import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A pool for [ByteBuffer]s. As they may use up a lot of memory,
|
||||
* they should be reused as efficiently as possible.
|
||||
*/
|
||||
object BufferPool {
|
||||
private val LOG = LoggerFactory.getLogger(BufferPool::class.java)
|
||||
|
||||
private val pools = mapOf(
|
||||
HEADER_SIZE to Stack<ByteBuffer>(),
|
||||
54 to Stack<ByteBuffer>(),
|
||||
1000 to Stack<ByteBuffer>(),
|
||||
60000 to Stack<ByteBuffer>(),
|
||||
MAX_PAYLOAD_SIZE to Stack<ByteBuffer>()
|
||||
)
|
||||
|
||||
@Synchronized fun allocate(capacity: Int): ByteBuffer {
|
||||
val targetSize = getTargetSize(capacity)
|
||||
val pool = pools[targetSize] ?: throw IllegalStateException("No pool for size $targetSize available")
|
||||
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
|
||||
*/
|
||||
@Synchronized fun allocateHeaderBuffer(): ByteBuffer {
|
||||
val pool = pools[HEADER_SIZE]
|
||||
if (pool == null || pool.isEmpty()) {
|
||||
return ByteBuffer.allocate(HEADER_SIZE)
|
||||
} else {
|
||||
return pool.pop()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized fun deallocate(buffer: ByteBuffer) {
|
||||
buffer.clear()
|
||||
val pool = pools[buffer.capacity()] ?: throw IllegalArgumentException("Illegal buffer capacity ${buffer.capacity()} one of ${pools.keys} expected.")
|
||||
pool.push(buffer)
|
||||
}
|
||||
|
||||
private fun getTargetSize(capacity: Int): Int {
|
||||
for (size in pools.keys) {
|
||||
if (size >= capacity) return size
|
||||
}
|
||||
throw IllegalArgumentException("Requested capacity too large: requested=$capacity; max=$MAX_PAYLOAD_SIZE")
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.factory
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||
import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
||||
import ch.dissem.bitmessage.entity.valueobject.extended.Vote
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.utils.Strings.str
|
||||
import ch.dissem.msgpack.Reader
|
||||
import ch.dissem.msgpack.types.MPMap
|
||||
import ch.dissem.msgpack.types.MPString
|
||||
import ch.dissem.msgpack.types.MPType
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.util.*
|
||||
import java.util.zip.InflaterInputStream
|
||||
|
||||
/**
|
||||
* Factory that creates [ExtendedEncoding] objects from byte arrays. You can register your own types by adding a
|
||||
* [ExtendedEncoding.Unpacker] using [.registerFactory].
|
||||
*/
|
||||
object ExtendedEncodingFactory {
|
||||
|
||||
private val LOG = LoggerFactory.getLogger(ExtendedEncodingFactory::class.java)
|
||||
private val KEY_MESSAGE_TYPE = MPString("")
|
||||
|
||||
private val factories = HashMap<String, ExtendedEncoding.Unpacker<*>>()
|
||||
|
||||
init {
|
||||
registerFactory(Message.Unpacker())
|
||||
registerFactory(Vote.Unpacker())
|
||||
}
|
||||
|
||||
fun registerFactory(factory: ExtendedEncoding.Unpacker<*>) {
|
||||
factories.put(factory.type, factory)
|
||||
}
|
||||
|
||||
|
||||
fun unzip(zippedData: ByteArray): ExtendedEncoding? {
|
||||
try {
|
||||
InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper ->
|
||||
val reader = Reader.getInstance()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val map = reader.read(unzipper) as MPMap<MPString, MPType<*>>
|
||||
val messageType = map[KEY_MESSAGE_TYPE]
|
||||
if (messageType == null) {
|
||||
LOG.error("Missing message type")
|
||||
return null
|
||||
}
|
||||
val factory = factories[str(messageType)]
|
||||
return ExtendedEncoding(
|
||||
factory?.unpack(map) ?: return null
|
||||
)
|
||||
}
|
||||
} catch (e: ClassCastException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.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.payload.ObjectType.*
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||
import ch.dissem.bitmessage.exception.NodeException
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.SocketException
|
||||
import java.net.SocketTimeoutException
|
||||
|
||||
/**
|
||||
* Creates [NetworkMessage] objects from [InputStreams][InputStream]
|
||||
*/
|
||||
object Factory {
|
||||
private val LOG = LoggerFactory.getLogger(Factory::class.java)
|
||||
|
||||
@Throws(SocketTimeoutException::class)
|
||||
@JvmStatic fun getNetworkMessage(@Suppress("UNUSED_PARAMETER") version: Int, stream: InputStream): NetworkMessage? {
|
||||
try {
|
||||
return V3MessageFactory.read(stream)
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is SocketTimeoutException,
|
||||
is NodeException -> throw e
|
||||
is SocketException -> throw NodeException(e.message, e)
|
||||
else -> {
|
||||
LOG.error(e.message, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic fun getObjectMessage(@Suppress("UNUSED_PARAMETER") version: Int, stream: InputStream, length: Int): ObjectMessage? {
|
||||
try {
|
||||
return V3MessageFactory.readObject(stream, length)
|
||||
} catch (e: IOException) {
|
||||
LOG.error(e.message, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
|
||||
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
|
||||
Pubkey.Feature.bitfield(*features))
|
||||
}
|
||||
|
||||
@JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long, behaviourBitfield: Int): Pubkey {
|
||||
if (publicSigningKey.size != 64 && publicSigningKey.size != 65)
|
||||
throw IllegalArgumentException("64 bytes signing key expected, but it was "
|
||||
+ publicSigningKey.size + " bytes long.")
|
||||
if (publicEncryptionKey.size != 64 && publicEncryptionKey.size != 65)
|
||||
throw IllegalArgumentException("64 bytes encryption key expected, but it was "
|
||||
+ publicEncryptionKey.size + " bytes long.")
|
||||
|
||||
when (version.toInt()) {
|
||||
2 -> return V2Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(behaviourBitfield)
|
||||
.build()
|
||||
3 -> return V3Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(behaviourBitfield)
|
||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||
.extraBytes(extraBytes)
|
||||
.build()
|
||||
4 -> return V4Pubkey(
|
||||
V3Pubkey.Builder()
|
||||
.stream(stream)
|
||||
.publicSigningKey(publicSigningKey)
|
||||
.publicEncryptionKey(publicEncryptionKey)
|
||||
.behaviorBitfield(behaviourBitfield)
|
||||
.nonceTrialsPerByte(nonceTrialsPerByte)
|
||||
.extraBytes(extraBytes)
|
||||
.build()
|
||||
)
|
||||
else -> throw IllegalArgumentException("Unexpected pubkey version " + version)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun createIdentityFromPrivateKey(address: String,
|
||||
privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long,
|
||||
behaviourBitfield: Int): BitmessageAddress {
|
||||
val temp = BitmessageAddress(address)
|
||||
val privateKey = PrivateKey(privateSigningKey, privateEncryptionKey,
|
||||
createPubkey(temp.version, temp.stream,
|
||||
cryptography().createPublicKey(privateSigningKey),
|
||||
cryptography().createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, behaviourBitfield))
|
||||
val result = BitmessageAddress(privateKey)
|
||||
if (result.address != address) {
|
||||
throw IllegalArgumentException("Address not matching private key. Address: " + address
|
||||
+ "; Address derived from private key: " + result.address)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic fun generatePrivateAddress(shorter: Boolean,
|
||||
stream: Long,
|
||||
vararg features: Pubkey.Feature): BitmessageAddress {
|
||||
return BitmessageAddress(PrivateKey(shorter, stream, 1000, 1000, *features))
|
||||
}
|
||||
|
||||
@JvmStatic fun getObjectPayload(objectType: Long,
|
||||
version: Long,
|
||||
streamNumber: Long,
|
||||
stream: InputStream,
|
||||
length: Int): ObjectPayload {
|
||||
val type = ObjectType.fromNumber(objectType)
|
||||
if (type != null) {
|
||||
when (type) {
|
||||
GET_PUBKEY -> return parseGetPubkey(version, streamNumber, stream, length)
|
||||
PUBKEY -> return parsePubkey(version, streamNumber, stream, length)
|
||||
MSG -> return parseMsg(version, streamNumber, stream, length)
|
||||
BROADCAST -> return parseBroadcast(version, streamNumber, stream, length)
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
||||
@JvmStatic private fun parseGetPubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
|
||||
return GetPubkey.read(stream, streamNumber, length, version)
|
||||
}
|
||||
|
||||
@JvmStatic fun readPubkey(version: Long, stream: Long, `is`: InputStream, length: Int, encrypted: Boolean): Pubkey? {
|
||||
when (version.toInt()) {
|
||||
2 -> return V2Pubkey.read(`is`, stream)
|
||||
3 -> return V3Pubkey.read(`is`, stream)
|
||||
4 -> return V4Pubkey.read(`is`, stream, length, encrypted)
|
||||
}
|
||||
LOG.debug("Unexpected pubkey version $version, handling as generic payload object")
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic private fun parsePubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
|
||||
val pubkey = readPubkey(version, streamNumber, stream, length, true)
|
||||
return pubkey ?: GenericPayload.read(version, streamNumber, stream, length)
|
||||
}
|
||||
|
||||
@JvmStatic private fun parseMsg(@Suppress("UNUSED_PARAMETER") version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
|
||||
return Msg.read(stream, streamNumber, length)
|
||||
}
|
||||
|
||||
@JvmStatic private fun parseBroadcast(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
|
||||
when (version.toInt()) {
|
||||
4 -> return V4Broadcast.read(stream, streamNumber, length)
|
||||
5 -> return V5Broadcast.read(stream, streamNumber, length)
|
||||
else -> {
|
||||
LOG.debug("Encountered unknown broadcast version " + version)
|
||||
return GenericPayload.read(version, streamNumber, stream, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun getBroadcast(plaintext: Plaintext): Broadcast {
|
||||
val sendingAddress = plaintext.from
|
||||
if (sendingAddress.version < 4) {
|
||||
return V4Broadcast(sendingAddress, plaintext)
|
||||
} else {
|
||||
return V5Broadcast(sendingAddress, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun createAck(from: BitmessageAddress, ackData: ByteArray?, ttl: Long): ObjectMessage? {
|
||||
val ack = GenericPayload(
|
||||
3, from.stream,
|
||||
ackData ?: return null
|
||||
)
|
||||
return ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now + ttl).build()
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.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 ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Creates protocol v3 network messages from [InputStreams][InputStream]
|
||||
*/
|
||||
object V3MessageFactory {
|
||||
private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun read(`in`: InputStream): NetworkMessage? {
|
||||
findMagic(`in`)
|
||||
val command = getCommand(`in`)
|
||||
val length = Decode.uint32(`in`).toInt()
|
||||
if (length > 1600003) {
|
||||
throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.")
|
||||
}
|
||||
val checksum = Decode.bytes(`in`, 4)
|
||||
|
||||
val payloadBytes = Decode.bytes(`in`, length)
|
||||
|
||||
if (testChecksum(checksum, payloadBytes)) {
|
||||
val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length)
|
||||
if (payload != null)
|
||||
return NetworkMessage(payload)
|
||||
else
|
||||
return null
|
||||
} else {
|
||||
throw IOException("Checksum failed for message '$command'")
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? {
|
||||
when (command) {
|
||||
"version" -> return parseVersion(stream)
|
||||
"verack" -> return VerAck()
|
||||
"addr" -> return parseAddr(stream)
|
||||
"inv" -> return parseInv(stream)
|
||||
"getdata" -> return parseGetData(stream)
|
||||
"object" -> return readObject(stream, length)
|
||||
"custom" -> return readCustom(stream, length)
|
||||
else -> {
|
||||
LOG.debug("Unknown command: " + command)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readCustom(`in`: InputStream, length: Int): MessagePayload {
|
||||
return CustomMessage.read(`in`, length)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun readObject(`in`: InputStream, length: Int): ObjectMessage {
|
||||
val counter = AccessCounter()
|
||||
val nonce = Decode.bytes(`in`, 8, counter)
|
||||
val expiresTime = Decode.int64(`in`, counter)
|
||||
val objectType = Decode.uint32(`in`, counter)
|
||||
val version = Decode.varInt(`in`, counter)
|
||||
val stream = Decode.varInt(`in`, counter)
|
||||
|
||||
val data = Decode.bytes(`in`, length - counter.length())
|
||||
var payload: ObjectPayload
|
||||
try {
|
||||
val dataStream = ByteArrayInputStream(data)
|
||||
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size)
|
||||
} catch (e: Exception) {
|
||||
if (LOG.isTraceEnabled) {
|
||||
LOG.trace("Could not parse object payload - using generic payload instead", e)
|
||||
LOG.trace(Strings.hex(data))
|
||||
}
|
||||
payload = GenericPayload(version, stream, data)
|
||||
}
|
||||
|
||||
return ObjectMessage.Builder()
|
||||
.nonce(nonce)
|
||||
.expiresTime(expiresTime)
|
||||
.objectType(objectType)
|
||||
.stream(stream)
|
||||
.payload(payload)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun parseGetData(stream: InputStream): GetData {
|
||||
val count = Decode.varInt(stream)
|
||||
val inventoryVectors = LinkedList<InventoryVector>()
|
||||
for (i in 0..count - 1) {
|
||||
inventoryVectors.add(parseInventoryVector(stream))
|
||||
}
|
||||
return GetData(inventoryVectors)
|
||||
}
|
||||
|
||||
private fun parseInv(stream: InputStream): Inv {
|
||||
val count = Decode.varInt(stream)
|
||||
val inventoryVectors = LinkedList<InventoryVector>()
|
||||
for (i in 0..count - 1) {
|
||||
inventoryVectors.add(parseInventoryVector(stream))
|
||||
}
|
||||
return Inv(inventoryVectors)
|
||||
}
|
||||
|
||||
private fun parseAddr(stream: InputStream): Addr {
|
||||
val count = Decode.varInt(stream)
|
||||
val networkAddresses = LinkedList<NetworkAddress>()
|
||||
for (i in 0..count - 1) {
|
||||
networkAddresses.add(parseAddress(stream, false))
|
||||
}
|
||||
return Addr(networkAddresses)
|
||||
}
|
||||
|
||||
private fun parseVersion(stream: InputStream): Version {
|
||||
val version = Decode.int32(stream)
|
||||
val services = Decode.int64(stream)
|
||||
val timestamp = Decode.int64(stream)
|
||||
val addrRecv = parseAddress(stream, true)
|
||||
val addrFrom = parseAddress(stream, true)
|
||||
val nonce = Decode.int64(stream)
|
||||
val userAgent = Decode.varString(stream)
|
||||
val streamNumbers = Decode.varIntList(stream)
|
||||
|
||||
return Version.Builder()
|
||||
.version(version)
|
||||
.services(services)
|
||||
.timestamp(timestamp)
|
||||
.addrRecv(addrRecv).addrFrom(addrFrom)
|
||||
.nonce(nonce)
|
||||
.userAgent(userAgent)
|
||||
.streams(*streamNumbers).build()
|
||||
}
|
||||
|
||||
private fun parseInventoryVector(stream: InputStream): InventoryVector {
|
||||
return InventoryVector(Decode.bytes(stream, 32))
|
||||
}
|
||||
|
||||
private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress {
|
||||
val time: Long
|
||||
val streamNumber: Long
|
||||
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
|
||||
}
|
||||
val services = Decode.int64(stream)
|
||||
val ipv6 = Decode.bytes(stream, 16)
|
||||
val port = Decode.uint16(stream)
|
||||
return NetworkAddress.Builder()
|
||||
.time(time)
|
||||
.stream(streamNumber)
|
||||
.services(services)
|
||||
.ipv6(ipv6)
|
||||
.port(port)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean {
|
||||
val payloadChecksum = cryptography().sha512(payload)
|
||||
for (i in checksum.indices) {
|
||||
if (checksum[i] != payloadChecksum[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getCommand(stream: InputStream): String {
|
||||
val bytes = ByteArray(12)
|
||||
var end = bytes.size
|
||||
for (i in bytes.indices) {
|
||||
bytes[i] = stream.read().toByte()
|
||||
if (end == bytes.size) {
|
||||
if (bytes[i].toInt() == 0) end = i
|
||||
} else {
|
||||
if (bytes[i].toInt() != 0) throw IOException("'\\u0000' padding expected for command")
|
||||
}
|
||||
}
|
||||
return String(bytes, 0, end, Charsets.US_ASCII)
|
||||
}
|
||||
|
||||
private fun findMagic(`in`: InputStream) {
|
||||
var pos = 0
|
||||
for (i in 0..1619999) {
|
||||
val b = `in`.read().toByte()
|
||||
if (b == NetworkMessage.MAGIC_BYTES[pos]) {
|
||||
if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) {
|
||||
return
|
||||
}
|
||||
} else if (pos > 0 && b == NetworkMessage.MAGIC_BYTES[0]) {
|
||||
pos = 1
|
||||
} else {
|
||||
pos = 0
|
||||
}
|
||||
pos++
|
||||
}
|
||||
throw NodeException("Failed to find MAGIC bytes in stream")
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.factory
|
||||
|
||||
import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE
|
||||
import ch.dissem.bitmessage.entity.NetworkMessage
|
||||
import ch.dissem.bitmessage.exception.NodeException
|
||||
import ch.dissem.bitmessage.utils.Decode
|
||||
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message.
|
||||
*/
|
||||
class V3MessageReader {
|
||||
private var headerBuffer: ByteBuffer? = null
|
||||
private var dataBuffer: ByteBuffer? = null
|
||||
|
||||
private var state: ReaderState? = ReaderState.MAGIC
|
||||
private var command: String? = null
|
||||
private var length: Int = 0
|
||||
private val checksum = ByteArray(4)
|
||||
|
||||
private val messages = LinkedList<NetworkMessage>()
|
||||
|
||||
fun getActiveBuffer(): ByteBuffer {
|
||||
if (state != null && state != ReaderState.DATA) {
|
||||
if (headerBuffer == null) {
|
||||
headerBuffer = BufferPool.allocateHeaderBuffer()
|
||||
}
|
||||
}
|
||||
return if (state == ReaderState.DATA)
|
||||
dataBuffer ?: throw IllegalStateException("data buffer is null")
|
||||
else
|
||||
headerBuffer ?: throw IllegalStateException("header buffer is null")
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (state != ReaderState.DATA) {
|
||||
getActiveBuffer() // in order to initialize
|
||||
headerBuffer?.flip() ?: throw IllegalStateException("header buffer is null")
|
||||
}
|
||||
when (state) {
|
||||
V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null"))
|
||||
V3MessageReader.ReaderState.HEADER -> header(headerBuffer ?: throw IllegalStateException("header buffer is null"))
|
||||
V3MessageReader.ReaderState.DATA -> data(dataBuffer ?: throw IllegalStateException("data buffer is null"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun magic(headerBuffer: ByteBuffer) {
|
||||
if (!findMagicBytes(headerBuffer)) {
|
||||
headerBuffer.compact()
|
||||
return
|
||||
} else {
|
||||
state = ReaderState.HEADER
|
||||
header(headerBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun header(headerBuffer: ByteBuffer) {
|
||||
if (headerBuffer.remaining() < 20) {
|
||||
headerBuffer.compact()
|
||||
headerBuffer.limit(20)
|
||||
return
|
||||
}
|
||||
command = getCommand(headerBuffer)
|
||||
length = Decode.uint32(headerBuffer).toInt()
|
||||
if (length > MAX_PAYLOAD_SIZE) {
|
||||
throw NodeException("Payload of " + length + " bytes received, no more than " +
|
||||
MAX_PAYLOAD_SIZE + " was expected.")
|
||||
}
|
||||
headerBuffer.get(checksum)
|
||||
state = ReaderState.DATA
|
||||
this.headerBuffer = null
|
||||
BufferPool.deallocate(headerBuffer)
|
||||
val dataBuffer = BufferPool.allocate(length)
|
||||
this.dataBuffer = dataBuffer
|
||||
dataBuffer.clear()
|
||||
dataBuffer.limit(length)
|
||||
data(dataBuffer)
|
||||
}
|
||||
|
||||
private fun data(dataBuffer: ByteBuffer) {
|
||||
if (dataBuffer.position() < length) {
|
||||
return
|
||||
} else {
|
||||
dataBuffer.flip()
|
||||
}
|
||||
if (!testChecksum(dataBuffer)) {
|
||||
state = ReaderState.MAGIC
|
||||
this.dataBuffer = null
|
||||
BufferPool.deallocate(dataBuffer)
|
||||
throw NodeException("Checksum failed for message '$command'")
|
||||
}
|
||||
try {
|
||||
V3MessageFactory.getPayload(
|
||||
command ?: throw IllegalStateException("command is null"),
|
||||
ByteArrayInputStream(dataBuffer.array(),
|
||||
dataBuffer.arrayOffset() + dataBuffer.position(), length),
|
||||
length
|
||||
)?.let { messages.add(NetworkMessage(it)) }
|
||||
} catch (e: IOException) {
|
||||
throw NodeException(e.message)
|
||||
} finally {
|
||||
state = ReaderState.MAGIC
|
||||
this.dataBuffer = null
|
||||
BufferPool.deallocate(dataBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessages(): MutableList<NetworkMessage> {
|
||||
return messages
|
||||
}
|
||||
|
||||
private fun findMagicBytes(buffer: ByteBuffer): Boolean {
|
||||
var i = 0
|
||||
while (buffer.hasRemaining()) {
|
||||
if (i == 0) {
|
||||
buffer.mark()
|
||||
}
|
||||
if (buffer.get() == NetworkMessage.MAGIC_BYTES[i]) {
|
||||
i++
|
||||
if (i == NetworkMessage.MAGIC_BYTES.size) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
i = 0
|
||||
}
|
||||
}
|
||||
if (i > 0) {
|
||||
buffer.reset()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getCommand(buffer: ByteBuffer): String {
|
||||
val start = buffer.position()
|
||||
var l = 0
|
||||
while (l < 12 && buffer.get().toInt() != 0) l++
|
||||
var i = l + 1
|
||||
while (i < 12) {
|
||||
if (buffer.get().toInt() != 0) throw NodeException("'\\u0000' padding expected for command")
|
||||
i++
|
||||
}
|
||||
return String(buffer.array(), start, l, Charsets.US_ASCII)
|
||||
}
|
||||
|
||||
private fun testChecksum(buffer: ByteBuffer): Boolean {
|
||||
val payloadChecksum = cryptography().sha512(buffer.array(),
|
||||
buffer.arrayOffset() + buffer.position(), length)
|
||||
for (i in checksum.indices) {
|
||||
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.
|
||||
*/
|
||||
fun cleanup() {
|
||||
state = null
|
||||
headerBuffer?.let { BufferPool.deallocate(it) }
|
||||
dataBuffer?.let { BufferPool.deallocate(it) }
|
||||
}
|
||||
|
||||
private enum class ReaderState {
|
||||
MAGIC, HEADER, DATA
|
||||
}
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.entity.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 ch.dissem.bitmessage.utils.max
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.math.BigInteger
|
||||
import java.security.*
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
||||
*/
|
||||
abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography, InternalContext.ContextHolder {
|
||||
private lateinit var ctx: InternalContext
|
||||
|
||||
@JvmField protected val ALGORITHM_ECDSA = "ECDSA"
|
||||
@JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
|
||||
@JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
override fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray {
|
||||
val mda = md("SHA-512")
|
||||
mda.update(data, offset, length)
|
||||
return mda.digest()
|
||||
}
|
||||
|
||||
override fun sha512(vararg data: ByteArray): ByteArray {
|
||||
return hash("SHA-512", *data)
|
||||
}
|
||||
|
||||
override fun doubleSha512(vararg data: ByteArray): ByteArray {
|
||||
val mda = md("SHA-512")
|
||||
for (d in data) {
|
||||
mda.update(d)
|
||||
}
|
||||
return mda.digest(mda.digest())
|
||||
}
|
||||
|
||||
override fun doubleSha512(data: ByteArray, length: Int): ByteArray {
|
||||
val mda = md("SHA-512")
|
||||
mda.update(data, 0, length)
|
||||
return mda.digest(mda.digest())
|
||||
}
|
||||
|
||||
override fun ripemd160(vararg data: ByteArray): ByteArray {
|
||||
return hash("RIPEMD160", *data)
|
||||
}
|
||||
|
||||
override fun doubleSha256(data: ByteArray, length: Int): ByteArray {
|
||||
val mda = md("SHA-256")
|
||||
mda.update(data, 0, length)
|
||||
return mda.digest(mda.digest())
|
||||
}
|
||||
|
||||
override fun sha1(vararg data: ByteArray): ByteArray {
|
||||
return hash("SHA-1", *data)
|
||||
}
|
||||
|
||||
override fun randomBytes(length: Int): ByteArray {
|
||||
val result = ByteArray(length)
|
||||
RANDOM.nextBytes(result)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||
extraBytes: Long, callback: ProofOfWorkEngine.Callback) {
|
||||
|
||||
val initialHash = getInitialHash(objectMessage)
|
||||
|
||||
val target = getProofOfWorkTarget(objectMessage,
|
||||
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES))
|
||||
|
||||
ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
|
||||
}
|
||||
|
||||
@Throws(InsufficientProofOfWorkException::class)
|
||||
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
||||
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||
val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage))
|
||||
if (Bytes.lt(target, value, 8)) {
|
||||
throw InsufficientProofOfWorkException(target, value)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doSign(data: ByteArray, privKey: java.security.PrivateKey): ByteArray {
|
||||
// TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network
|
||||
val sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider)
|
||||
sig.initSign(privKey)
|
||||
sig.update(data)
|
||||
return sig.sign()
|
||||
}
|
||||
|
||||
|
||||
protected fun doCheckSignature(data: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean {
|
||||
for (algorithm in arrayOf(ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256)) {
|
||||
val sig = Signature.getInstance(algorithm, provider)
|
||||
sig.initVerify(publicKey)
|
||||
sig.update(data)
|
||||
if (sig.verify(signature)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getInitialHash(objectMessage: ObjectMessage): ByteArray {
|
||||
return sha512(objectMessage.payloadBytesWithoutNonce)
|
||||
}
|
||||
|
||||
override fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes
|
||||
|
||||
val TTL = BigInteger.valueOf(objectMessage.expiresTime - UnixTime.now)
|
||||
val powLength = BigInteger.valueOf(objectMessage.payloadBytesWithoutNonce.size + extraBytes)
|
||||
val 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 fun hash(algorithm: String, vararg data: ByteArray): ByteArray {
|
||||
val mda = md(algorithm)
|
||||
for (d in data) {
|
||||
mda.update(d)
|
||||
}
|
||||
return mda.digest()
|
||||
}
|
||||
|
||||
private fun md(algorithm: String): MessageDigest {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm, provider)
|
||||
} catch (e: GeneralSecurityException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun mac(key_m: ByteArray, data: ByteArray): ByteArray {
|
||||
try {
|
||||
val mac = Mac.getInstance("HmacSHA256", provider)
|
||||
mac.init(SecretKeySpec(key_m, "HmacSHA256"))
|
||||
return mac.doFinal(data)
|
||||
} catch (e: GeneralSecurityException) {
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
|
||||
return Factory.createPubkey(version, stream,
|
||||
createPublicKey(privateSigningKey),
|
||||
createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, *features)
|
||||
}
|
||||
|
||||
override fun keyToBigInt(privateKey: ByteArray): BigInteger {
|
||||
return BigInteger(1, privateKey)
|
||||
}
|
||||
|
||||
override fun randomNonce(): Long {
|
||||
return RANDOM.nextLong()
|
||||
}
|
||||
|
||||
companion object {
|
||||
protected val LOG = LoggerFactory.getLogger(Cryptography::class.java)!!
|
||||
private val RANDOM = SecureRandom()
|
||||
private val TWO = BigInteger.valueOf(2)
|
||||
private val TWO_POW_64 = TWO.pow(64)!!
|
||||
private val TWO_POW_16 = TWO.pow(16)!!
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.utils.SqlStrings.join
|
||||
import ch.dissem.bitmessage.utils.Strings
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractMessageRepository : MessageRepository, InternalContext.ContextHolder {
|
||||
protected lateinit var ctx: InternalContext
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
protected fun saveContactIfNecessary(contact: BitmessageAddress?) {
|
||||
contact?.let {
|
||||
val savedAddress = ctx.addressRepository.getAddress(it.address)
|
||||
if (savedAddress == null) {
|
||||
ctx.addressRepository.save(it)
|
||||
} else {
|
||||
if (savedAddress.pubkey == null && it.pubkey != null) {
|
||||
savedAddress.pubkey = it.pubkey
|
||||
ctx.addressRepository.save(savedAddress)
|
||||
}
|
||||
it.alias = savedAddress.alias
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAllMessages() = find("1=1")
|
||||
|
||||
override fun getMessage(id: Any): Plaintext {
|
||||
if (id is Long) {
|
||||
return single(find("id=" + id)) ?: throw IllegalArgumentException("There is no message with id $id")
|
||||
} else {
|
||||
throw IllegalArgumentException("Long expected for ID")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMessage(iv: InventoryVector): Plaintext? {
|
||||
return single(find("iv=X'${Strings.hex(iv.hash)}'"))
|
||||
}
|
||||
|
||||
override fun getMessage(initialHash: ByteArray): Plaintext? {
|
||||
return single(find("initial_hash=X'${Strings.hex(initialHash)}'"))
|
||||
}
|
||||
|
||||
override fun getMessageForAck(ackData: ByteArray): Plaintext? {
|
||||
return single(find("ack_data=X'${Strings.hex(ackData)}' AND status='${Plaintext.Status.SENT}'"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds messages that have a specific label, with optional offset and limit. If the limit is set to 0,
|
||||
* offset and limit are ignored.
|
||||
*/
|
||||
open fun findMessages(label: Label?, offset: Int = 0, limit: Int = 0) = if (label == null) {
|
||||
find("id NOT IN (SELECT message_id FROM Message_Label)", offset, limit)
|
||||
} else {
|
||||
find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")", offset, limit)
|
||||
}
|
||||
|
||||
override fun findMessages(label: Label?) = if (label == null) {
|
||||
find("id NOT IN (SELECT message_id FROM Message_Label)")
|
||||
} else {
|
||||
find("id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id})")
|
||||
}
|
||||
|
||||
override fun findMessages(status: Plaintext.Status, recipient: BitmessageAddress): List<Plaintext> {
|
||||
return find("status='${status.name}' AND recipient='${recipient.address}'")
|
||||
}
|
||||
|
||||
override fun findMessages(status: Plaintext.Status): List<Plaintext> {
|
||||
return find("status='${status.name}'")
|
||||
}
|
||||
|
||||
override fun findMessages(sender: BitmessageAddress): List<Plaintext> {
|
||||
return find("sender='${sender.address}'")
|
||||
}
|
||||
|
||||
override fun findMessagesToResend(): List<Plaintext> {
|
||||
return find("status='${Plaintext.Status.SENT.name}' AND next_try < ${UnixTime.now}")
|
||||
}
|
||||
|
||||
override fun findResponses(parent: Plaintext): List<Plaintext> {
|
||||
if (parent.inventoryVector == null) {
|
||||
return emptyList()
|
||||
}
|
||||
return find("iv IN (SELECT child FROM Message_Parent WHERE parent=X'${Strings.hex(parent.inventoryVector!!.hash)}')")
|
||||
}
|
||||
|
||||
override fun getConversation(conversationId: UUID): List<Plaintext> {
|
||||
return find("conversation=X'${conversationId.toString().replace("-", "")}'")
|
||||
}
|
||||
|
||||
override fun getLabels(): List<Label> {
|
||||
return findLabels("1=1")
|
||||
}
|
||||
|
||||
override fun getLabels(vararg types: Label.Type): List<Label> {
|
||||
return findLabels("type IN (${join(*types)})")
|
||||
}
|
||||
|
||||
protected abstract fun findLabels(where: String): List<Label>
|
||||
|
||||
|
||||
protected fun <T> single(collection: Collection<T>): T? {
|
||||
return when (collection.size) {
|
||||
0 -> null
|
||||
1 -> collection.iterator().next()
|
||||
else -> throw ApplicationException("This shouldn't happen, found ${collection.size} items, one or none was expected")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds messages that mach the given where statement, with optional offset and limit. If the limit is set to 0,
|
||||
* offset and limit are ignored.
|
||||
*/
|
||||
protected abstract fun find(where: String, offset: Int = 0, limit: Int = 0): List<Plaintext>
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||
|
||||
interface AddressRepository {
|
||||
/**
|
||||
* Returns a matching BitmessageAddress if there is one with the given ripe or tag, that
|
||||
* has no public key yet. If it doesn't exist or already has a public key, null is returned.
|
||||
|
||||
* @param ripeOrTag Either ripe or tag (depending of address version) of an address with
|
||||
* * missing public key.
|
||||
* *
|
||||
* @return the matching address if there is one without public key, or null otherwise.
|
||||
*/
|
||||
fun findContact(ripeOrTag: ByteArray): BitmessageAddress?
|
||||
|
||||
fun findIdentity(ripeOrTag: ByteArray): BitmessageAddress?
|
||||
|
||||
/**
|
||||
* @return all Bitmessage addresses that belong to this user, i.e. have a private key.
|
||||
*/
|
||||
fun getIdentities(): List<BitmessageAddress>
|
||||
|
||||
/**
|
||||
* @return all subscribed chans.
|
||||
*/
|
||||
fun getChans(): List<BitmessageAddress>
|
||||
|
||||
fun getSubscriptions(): List<BitmessageAddress>
|
||||
|
||||
fun getSubscriptions(broadcastVersion: Long): List<BitmessageAddress>
|
||||
|
||||
/**
|
||||
* @return all Bitmessage addresses that have no private key or are chans.
|
||||
*/
|
||||
fun getContacts(): List<BitmessageAddress>
|
||||
|
||||
/**
|
||||
* Implementations must not delete cryptographic keys if they're not provided by `address`.
|
||||
|
||||
* @param address to save or update
|
||||
*/
|
||||
fun save(address: BitmessageAddress)
|
||||
|
||||
fun remove(address: BitmessageAddress)
|
||||
|
||||
fun getAddress(address: String): BitmessageAddress?
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
/**
|
||||
* Should be thrown if a received and decrypted message can't be stored because it has already been received and stored.
|
||||
* (So it's not announced again to the client.)
|
||||
*/
|
||||
class AlreadyStoredException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
||||
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
||||
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 [SecureRandom],
|
||||
* which should be secure enough.
|
||||
*/
|
||||
interface Cryptography {
|
||||
/**
|
||||
* A helper method to calculate SHA-512 hashes. Please note that a new [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
|
||||
*/
|
||||
fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray
|
||||
|
||||
/**
|
||||
* A helper method to calculate SHA-512 hashes. Please note that a new [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
|
||||
*/
|
||||
fun sha512(vararg data: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* A helper method to calculate doubleSHA-512 hashes. Please note that a new [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
|
||||
*/
|
||||
fun doubleSha512(vararg data: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Please note that a new [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.
|
||||
*
|
||||
|
||||
* @param data to get hashed
|
||||
* *
|
||||
* @param length number of bytes to be taken into account
|
||||
* *
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
fun doubleSha512(data: ByteArray, length: Int): ByteArray
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Please note that a new [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.
|
||||
*
|
||||
|
||||
* @param data to get hashed
|
||||
* *
|
||||
* @return RIPEMD-160 hash of data
|
||||
*/
|
||||
fun ripemd160(vararg data: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Please note that a new [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.
|
||||
*
|
||||
|
||||
* @param data to get hashed
|
||||
* *
|
||||
* @param length number of bytes to be taken into account
|
||||
* *
|
||||
* @return SHA-256 hash of data
|
||||
*/
|
||||
fun doubleSha256(data: ByteArray, length: Int): ByteArray
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Please note that a new [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.
|
||||
*
|
||||
|
||||
* @param data to get hashed
|
||||
* *
|
||||
* @return SHA hash of data
|
||||
*/
|
||||
fun sha1(vararg data: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* @param length number of bytes to return
|
||||
* *
|
||||
* @return an array of the given size containing random bytes
|
||||
*/
|
||||
fun randomBytes(length: Int): ByteArray
|
||||
|
||||
/**
|
||||
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
|
||||
* live.
|
||||
|
||||
* @param objectMessage 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
|
||||
*/
|
||||
fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
||||
extraBytes: Long, callback: ProofOfWorkEngine.Callback)
|
||||
|
||||
/**
|
||||
* @param objectMessage 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)
|
||||
*/
|
||||
@Throws(InsufficientProofOfWorkException::class)
|
||||
fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long)
|
||||
|
||||
fun getInitialHash(objectMessage: ObjectMessage): ByteArray
|
||||
|
||||
fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun mac(key_m: ByteArray, data: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* @param encrypt if true, encrypts data, otherwise tries to decrypt it.
|
||||
* *
|
||||
* @param data
|
||||
* *
|
||||
* @param key_e
|
||||
* *
|
||||
* @return
|
||||
*/
|
||||
fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
||||
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* *
|
||||
* @return a public key corresponding to the given private key
|
||||
*/
|
||||
fun createPublicKey(privateKey: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* *
|
||||
* @return a big integer representation (unsigned) of the given bytes
|
||||
*/
|
||||
fun keyToBigInt(privateKey: ByteArray): BigInteger
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
fun isSignatureValid(data: ByteArray, signature: ByteArray, pubkey: Pubkey): Boolean
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun getSignature(data: ByteArray, privateKey: ch.dissem.bitmessage.entity.valueobject.PrivateKey): ByteArray
|
||||
|
||||
/**
|
||||
* @return a random number of type long
|
||||
*/
|
||||
fun randomNonce(): Long
|
||||
|
||||
fun multiply(k: ByteArray, r: ByteArray): ByteArray
|
||||
|
||||
fun createPoint(x: ByteArray, y: ByteArray): ByteArray
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.entity.CustomMessage
|
||||
import ch.dissem.bitmessage.entity.MessagePayload
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
interface CustomCommandHandler {
|
||||
fun handle(request: CustomMessage): MessagePayload?
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Status.*
|
||||
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
|
||||
open class DefaultLabeler : Labeler, InternalContext.ContextHolder {
|
||||
private lateinit var ctx: InternalContext
|
||||
|
||||
var listener: ((message: Plaintext, added: Collection<Label>, removed: Collection<Label>) -> Unit)? = null
|
||||
|
||||
override fun setContext(context: InternalContext) {
|
||||
ctx = context
|
||||
}
|
||||
|
||||
override fun setLabels(msg: Plaintext) {
|
||||
msg.status = RECEIVED
|
||||
val labelsToAdd =
|
||||
if (msg.type == BROADCAST) {
|
||||
ctx.messageRepository.getLabels(Label.Type.BROADCAST, Label.Type.UNREAD)
|
||||
} else {
|
||||
ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)
|
||||
}
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||
}
|
||||
|
||||
override fun markAsDraft(msg: Plaintext) {
|
||||
msg.status = DRAFT
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.DRAFT)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||
}
|
||||
|
||||
override fun markAsSending(msg: Plaintext) {
|
||||
if (msg.to != null && msg.to!!.pubkey == null) {
|
||||
msg.status = PUBKEY_REQUESTED
|
||||
} else {
|
||||
msg.status = DOING_PROOF_OF_WORK
|
||||
}
|
||||
val labelsToRemove = msg.labels.filter { it.type == Label.Type.DRAFT }
|
||||
msg.removeLabel(Label.Type.DRAFT)
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.OUTBOX)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||
}
|
||||
|
||||
override fun markAsSent(msg: Plaintext) {
|
||||
msg.status = SENT
|
||||
val labelsToRemove = msg.labels.filter { it.type == Label.Type.OUTBOX }
|
||||
msg.removeLabel(Label.Type.OUTBOX)
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.SENT)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||
}
|
||||
|
||||
override fun markAsAcknowledged(msg: Plaintext) {
|
||||
msg.status = SENT_ACKNOWLEDGED
|
||||
}
|
||||
|
||||
override fun markAsRead(msg: Plaintext) {
|
||||
val labelsToRemove = msg.labels.filter { it.type == Label.Type.UNREAD }
|
||||
msg.removeLabel(Label.Type.UNREAD)
|
||||
listener?.invoke(msg, emptyList(), labelsToRemove)
|
||||
}
|
||||
|
||||
override fun markAsUnread(msg: Plaintext) {
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.UNREAD)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, emptyList())
|
||||
}
|
||||
|
||||
override fun delete(msg: Plaintext) {
|
||||
val labelsToRemove = msg.labels.toSet()
|
||||
msg.labels.clear()
|
||||
val labelsToAdd = ctx.messageRepository.getLabels(Label.Type.TRASH)
|
||||
msg.addLabels(labelsToAdd)
|
||||
listener?.invoke(msg, labelsToAdd, labelsToRemove)
|
||||
}
|
||||
|
||||
override fun archive(msg: Plaintext) {
|
||||
val labelsToRemove = msg.labels.toSet()
|
||||
msg.labels.clear()
|
||||
listener?.invoke(msg, emptyList(), labelsToRemove)
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||
|
||||
/**
|
||||
* The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing.
|
||||
*/
|
||||
interface Inventory {
|
||||
/**
|
||||
* Returns the IVs of all valid objects we have for the given streams
|
||||
*/
|
||||
fun getInventory(vararg streams: Long): List<InventoryVector>
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun getMissing(offer: List<InventoryVector>, vararg streams: Long): List<InventoryVector>
|
||||
|
||||
fun getObject(vector: InventoryVector): ObjectMessage?
|
||||
|
||||
/**
|
||||
* This method is mainly used to search for public keys to newly added addresses or broadcasts from new
|
||||
* subscriptions.
|
||||
*/
|
||||
fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage>
|
||||
|
||||
fun storeObject(objectMessage: ObjectMessage)
|
||||
|
||||
operator fun contains(objectMessage: ObjectMessage): Boolean
|
||||
|
||||
/**
|
||||
* Deletes all objects that expired 5 minutes ago or earlier
|
||||
* (so we don't accidentally request objects we just deleted)
|
||||
*/
|
||||
fun cleanup()
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.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 [DefaultLabeler] whenever possible,
|
||||
* instead of directly implementing the interface.
|
||||
*
|
||||
* As the labeler gets called whenever the state of a message changes, it can also be used
|
||||
* as a listener.
|
||||
*/
|
||||
interface Labeler {
|
||||
/**
|
||||
* Sets the labels of a newly received message.
|
||||
*
|
||||
* @param msg an unlabeled message or broadcast
|
||||
*/
|
||||
fun setLabels(msg: Plaintext)
|
||||
|
||||
fun markAsDraft(msg: Plaintext)
|
||||
|
||||
/**
|
||||
* It is paramount that this methods marks the [Plaintext] object with status
|
||||
* [Plaintext.Status.PUBKEY_REQUESTED] (see [DefaultLabeler])
|
||||
*/
|
||||
fun markAsSending(msg: Plaintext)
|
||||
|
||||
fun markAsSent(msg: Plaintext)
|
||||
|
||||
fun markAsAcknowledged(msg: Plaintext)
|
||||
|
||||
fun markAsRead(msg: Plaintext)
|
||||
|
||||
fun markAsUnread(msg: Plaintext)
|
||||
|
||||
fun delete(msg: Plaintext)
|
||||
|
||||
fun archive(msg: Plaintext)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.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.InventoryVector
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||
import java.util.*
|
||||
|
||||
interface MessageRepository {
|
||||
fun getLabels(): List<Label>
|
||||
|
||||
fun getLabels(vararg types: Label.Type): List<Label>
|
||||
|
||||
fun save(label: Label)
|
||||
|
||||
fun countUnread(label: Label?): Int
|
||||
|
||||
fun getAllMessages(): List<Plaintext>
|
||||
|
||||
fun getMessage(id: Any): Plaintext
|
||||
|
||||
fun getMessage(iv: InventoryVector): Plaintext?
|
||||
|
||||
fun getMessage(initialHash: ByteArray): Plaintext?
|
||||
|
||||
fun getMessageForAck(ackData: ByteArray): Plaintext?
|
||||
|
||||
/**
|
||||
* @param label to search for
|
||||
* *
|
||||
* @return a distinct list of all conversations that have at least one message with the given label.
|
||||
*/
|
||||
fun findConversations(label: Label?): List<UUID>
|
||||
|
||||
fun findMessages(label: Label?): List<Plaintext>
|
||||
|
||||
fun findMessages(status: Status): List<Plaintext>
|
||||
|
||||
fun findMessages(status: Status, recipient: BitmessageAddress): List<Plaintext>
|
||||
|
||||
fun findMessages(sender: BitmessageAddress): List<Plaintext>
|
||||
|
||||
fun findResponses(parent: Plaintext): List<Plaintext>
|
||||
|
||||
fun findMessagesToResend(): List<Plaintext>
|
||||
|
||||
fun save(message: Plaintext)
|
||||
|
||||
fun remove(message: Plaintext)
|
||||
|
||||
/**
|
||||
* Returns all messages with this conversation ID. The returned messages aren't sorted in any way,
|
||||
* so you may prefer to use [ch.dissem.bitmessage.utils.ConversationService.getConversation]
|
||||
* instead.
|
||||
|
||||
* @param conversationId ID of the requested conversation
|
||||
* *
|
||||
* @return all messages with the given conversation ID
|
||||
*/
|
||||
fun getConversation(conversationId: UUID): Collection<Plaintext>
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.exception.ApplicationException
|
||||
import ch.dissem.bitmessage.utils.Bytes
|
||||
import ch.dissem.bitmessage.utils.Bytes.inc
|
||||
import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
|
||||
/**
|
||||
* A POW engine using all available CPU cores.
|
||||
*/
|
||||
class MultiThreadedPOWEngine : ProofOfWorkEngine {
|
||||
private val waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build())
|
||||
private val 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 fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
|
||||
waiterPool.execute({
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
var cores = Runtime.getRuntime().availableProcessors()
|
||||
if (cores > 255) cores = 255
|
||||
LOG.info("Doing POW using $cores cores")
|
||||
val workers = ArrayList<Worker>(cores)
|
||||
for (i in 0..cores - 1) {
|
||||
val w = Worker(cores.toByte(), i, initialHash, target)
|
||||
workers.add(w)
|
||||
}
|
||||
val futures = ArrayList<Future<ByteArray>>(cores)
|
||||
// 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.
|
||||
workers.mapTo(futures) { workerPool.submit(it) }
|
||||
try {
|
||||
while (!Thread.interrupted()) {
|
||||
futures.firstOrNull { it.isDone }?.let {
|
||||
callback.onNonceCalculated(initialHash, it.get())
|
||||
LOG.info("Nonce calculated in " + (System.currentTimeMillis() - startTime) / 1000 + " seconds")
|
||||
futures.forEach { it.cancel(true) }
|
||||
return@execute
|
||||
}
|
||||
}
|
||||
LOG.error("POW waiter thread interrupted - this should not happen!")
|
||||
} catch (e: ExecutionException) {
|
||||
LOG.error(e.message, e)
|
||||
} catch (e: InterruptedException) {
|
||||
LOG.error("POW waiter thread interrupted - this should not happen!", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private inner class Worker internal constructor(
|
||||
private val numberOfCores: Byte, core: Int,
|
||||
private val initialHash: ByteArray,
|
||||
private val target: ByteArray
|
||||
) : Callable<ByteArray> {
|
||||
private val mda: MessageDigest
|
||||
private val nonce = ByteArray(8)
|
||||
|
||||
init {
|
||||
this.nonce[7] = core.toByte()
|
||||
try {
|
||||
mda = MessageDigest.getInstance("SHA-512")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
LOG.error(e.message, e)
|
||||
throw ApplicationException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun call(): ByteArray? {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine::class.java)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.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.concurrent.Future
|
||||
|
||||
/**
|
||||
* Handles incoming messages
|
||||
*/
|
||||
interface NetworkHandler {
|
||||
|
||||
/**
|
||||
* Connects to the trusted host, fetches and offers new messages and disconnects afterwards.
|
||||
*
|
||||
*
|
||||
* An implementation should disconnect if either the timeout is reached or the returned thread is interrupted.
|
||||
*
|
||||
*/
|
||||
fun synchronize(server: InetAddress, port: Int, timeoutInSeconds: Long): Future<*>
|
||||
|
||||
/**
|
||||
* Send a custom message to a specific node (that should implement handling for this message type) and returns
|
||||
* the response, which in turn is expected to be a [CustomMessage].
|
||||
|
||||
* @param server the node's address
|
||||
* *
|
||||
* @param port the node's port
|
||||
* *
|
||||
* @param request the request
|
||||
* *
|
||||
* @return the response
|
||||
*/
|
||||
fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage
|
||||
|
||||
/**
|
||||
* Start a full network node, accepting incoming connections and relaying objects.
|
||||
*/
|
||||
fun start()
|
||||
|
||||
/**
|
||||
* Stop the full network node.
|
||||
*/
|
||||
fun stop()
|
||||
|
||||
/**
|
||||
* Offer new objects to up to 8 random nodes.
|
||||
*/
|
||||
fun offer(iv: InventoryVector)
|
||||
|
||||
/**
|
||||
* Request each of those objects from a node that knows of the requested object.
|
||||
|
||||
* @param inventoryVectors of the objects to be requested
|
||||
*/
|
||||
fun request(inventoryVectors: MutableCollection<InventoryVector>)
|
||||
|
||||
fun getNetworkStatus(): Property
|
||||
|
||||
val isRunning: Boolean
|
||||
|
||||
interface MessageListener {
|
||||
@Throws(IOException::class)
|
||||
fun receive(objectMessage: ObjectMessage)
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||
|
||||
/**
|
||||
* Stores and provides known peers.
|
||||
*/
|
||||
interface NodeRegistry {
|
||||
/**
|
||||
* Removes all known nodes from registry. This should work around connection issues
|
||||
* when there are many invalid nodes in the registry.
|
||||
*/
|
||||
fun clear()
|
||||
|
||||
fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress>
|
||||
|
||||
fun offerAddresses(nodes: List<NetworkAddress>)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||
import ch.dissem.bitmessage.utils.UnixTime
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Helper class to kick start node registries.
|
||||
*/
|
||||
object NodeRegistryHelper {
|
||||
private val LOG = LoggerFactory.getLogger(NodeRegistryHelper::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun loadStableNodes(): Map<Long, Set<NetworkAddress>> {
|
||||
javaClass.classLoader.getResourceAsStream("nodes.txt").use { `in` ->
|
||||
val scanner = Scanner(`in`)
|
||||
var stream: Long = 0
|
||||
val result = HashMap<Long, Set<NetworkAddress>>()
|
||||
var streamSet: MutableSet<NetworkAddress>? = null
|
||||
while (scanner.hasNext()) {
|
||||
try {
|
||||
val line = scanner.nextLine().trim { it <= ' ' }
|
||||
if (line.startsWith("[stream")) {
|
||||
stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']')))
|
||||
streamSet = HashSet<NetworkAddress>()
|
||||
result.put(stream, streamSet)
|
||||
} else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) {
|
||||
val portIndex = line.lastIndexOf(':')
|
||||
val inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex))
|
||||
val port = Integer.valueOf(line.substring(portIndex + 1))!!
|
||||
inetAddresses.mapTo(streamSet) { NetworkAddress(
|
||||
time = UnixTime.now,
|
||||
stream = stream,
|
||||
inetAddress = it,
|
||||
port = port
|
||||
) }
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
LOG.warn(e.message, e)
|
||||
}
|
||||
}
|
||||
if (LOG.isDebugEnabled) {
|
||||
for ((key, value) in result) {
|
||||
LOG.debug("Stream " + key + ": loaded " + value.size + " bootstrap nodes.")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
/**
|
||||
* Does the proof of work necessary to send an object.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback)
|
||||
|
||||
interface Callback {
|
||||
/**
|
||||
* @param nonce 8 bytes nonce
|
||||
*/
|
||||
fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||
import ch.dissem.bitmessage.entity.Plaintext
|
||||
|
||||
/**
|
||||
* Objects that proof of work is currently being done for.
|
||||
|
||||
* @author Christian Basler
|
||||
*/
|
||||
interface ProofOfWorkRepository {
|
||||
fun getItem(initialHash: ByteArray): Item
|
||||
|
||||
fun getItems(): List<ByteArray>
|
||||
|
||||
fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long)
|
||||
|
||||
fun putObject(item: Item)
|
||||
|
||||
fun removeObject(initialHash: ByteArray)
|
||||
|
||||
data class Item @JvmOverloads constructor(
|
||||
val objectMessage: ObjectMessage,
|
||||
val nonceTrialsPerByte: Long,
|
||||
val extraBytes: Long,
|
||||
// Needed for ACK POW calculation
|
||||
val expirationTime: Long? = 0,
|
||||
val message: Plaintext? = null
|
||||
)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.ports
|
||||
|
||||
import ch.dissem.bitmessage.utils.Bytes
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
|
||||
*
|
||||
* **Warning:** implementations probably depend on POW being asynchronous, that's
|
||||
* another reason not to use this one.
|
||||
*/
|
||||
class SimplePOWEngine : ProofOfWorkEngine {
|
||||
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
|
||||
val mda = MessageDigest.getInstance("SHA-512")
|
||||
val nonce = ByteArray(8)
|
||||
do {
|
||||
Bytes.inc(nonce)
|
||||
mda.update(nonce)
|
||||
mda.update(initialHash)
|
||||
} while (Bytes.lt(target, mda.digest(mda.digest()), 8))
|
||||
callback.onNonceCalculated(initialHash, nonce)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
/**
|
||||
* Intended to count the bytes read or written during (de-)serialization.
|
||||
*/
|
||||
class AccessCounter {
|
||||
private var count: Int = 0
|
||||
|
||||
/**
|
||||
* Increases the counter by one.
|
||||
*/
|
||||
private fun inc() {
|
||||
count++
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the counter by length.
|
||||
*/
|
||||
private fun inc(length: Int) {
|
||||
count += length
|
||||
}
|
||||
|
||||
fun length(): Int {
|
||||
return count
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Increases the counter by one, if not null.
|
||||
*/
|
||||
@JvmStatic fun inc(counter: AccessCounter?) {
|
||||
counter?.inc()
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the counter by length, if not null.
|
||||
*/
|
||||
@JvmStatic fun inc(counter: AccessCounter?, length: Int) {
|
||||
counter?.inc(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Christian Basler
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package ch.dissem.bitmessage.utils
|
||||
|
||||
import ch.dissem.bitmessage.exception.AddressFormatException
|
||||
import 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.
|
||||
*/
|
||||
object Base58 {
|
||||
private val INDEXES = IntArray(128)
|
||||
private val ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray()
|
||||
|
||||
init {
|
||||
for (i in INDEXES.indices) {
|
||||
INDEXES[i] = -1
|
||||
}
|
||||
for (i in ALPHABET.indices) {
|
||||
INDEXES[ALPHABET[i].toInt()] = i
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given bytes in base58. No checksum is appended.
|
||||
|
||||
* @param data to encode
|
||||
* *
|
||||
* @return base58 encoded input
|
||||
*/
|
||||
@JvmStatic fun encode(data: ByteArray): String {
|
||||
if (data.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val bytes = copyOfRange(data, 0, data.size)
|
||||
// Count leading zeroes.
|
||||
var zeroCount = 0
|
||||
while (zeroCount < bytes.size && bytes[zeroCount].toInt() == 0) {
|
||||
++zeroCount
|
||||
}
|
||||
// The actual encoding.
|
||||
val temp = ByteArray(bytes.size * 2)
|
||||
var j = temp.size
|
||||
|
||||
var startAt = zeroCount
|
||||
while (startAt < bytes.size) {
|
||||
val mod = divmod58(bytes, startAt)
|
||||
if (bytes[startAt].toInt() == 0) {
|
||||
++startAt
|
||||
}
|
||||
temp[--j] = ALPHABET[mod.toInt()].toByte()
|
||||
}
|
||||
|
||||
// Strip extra '1' if there are some after decoding.
|
||||
while (j < temp.size && temp[j] == ALPHABET[0].toByte()) {
|
||||
++j
|
||||
}
|
||||
// Add as many leading '1' as there were leading zeros.
|
||||
while (--zeroCount >= 0) {
|
||||
temp[--j] = ALPHABET[0].toByte()
|
||||
}
|
||||
|
||||
val output = copyOfRange(temp, j, temp.size)
|
||||
return String(output, Charsets.US_ASCII)
|
||||
}
|
||||
|
||||
@Throws(AddressFormatException::class)
|
||||
@JvmStatic fun decode(input: String): ByteArray {
|
||||
if (input.isEmpty()) {
|
||||
return ByteArray(0)
|
||||
}
|
||||
val input58 = ByteArray(input.length)
|
||||
// Transform the String to a base58 byte sequence
|
||||
for (i in 0..input.length - 1) {
|
||||
val c = input[i].toInt()
|
||||
|
||||
val digit58 = if (c < 128) {
|
||||
INDEXES[c]
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
if (digit58 < 0) {
|
||||
throw AddressFormatException("Illegal character ${input[i]} at $i")
|
||||
}
|
||||
|
||||
input58[i] = digit58.toByte()
|
||||
}
|
||||
// Count leading zeroes
|
||||
var zeroCount = 0
|
||||
while (zeroCount < input58.size && input58[zeroCount] == 0.toByte()) {
|
||||
++zeroCount
|
||||
}
|
||||
// The encoding
|
||||
val temp = ByteArray(input.length)
|
||||
var j = temp.size
|
||||
|
||||
var startAt = zeroCount
|
||||
while (startAt < input58.size) {
|
||||
val mod = divmod256(input58, startAt)
|
||||
if (input58[startAt].toInt() == 0) {
|
||||
++startAt
|
||||
}
|
||||
|
||||
temp[--j] = mod
|
||||
}
|
||||
// Do no add extra leading zeroes, move j to first non null byte.
|
||||
while (j < temp.size && temp[j].toInt() == 0) {
|
||||
++j
|
||||
}
|
||||
return copyOfRange(temp, j - zeroCount, temp.size)
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 58, returns number % 58
|
||||
//
|
||||
private fun divmod58(number: ByteArray, startAt: Int): Byte {
|
||||
var remainder = 0
|
||||
for (i in startAt..number.size - 1) {
|
||||
val digit256 = number[i].toInt() and 0xFF
|
||||
val temp = remainder * 256 + digit256
|
||||
|
||||
number[i] = (temp / 58).toByte()
|
||||
|
||||
remainder = temp % 58
|
||||
}
|
||||
|
||||
return remainder.toByte()
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 256, returns number % 256
|
||||
//
|
||||
private fun divmod256(number58: ByteArray, startAt: Int): Byte {
|
||||
var remainder = 0
|
||||
for (i in startAt..number58.size - 1) {
|
||||
val digit58 = number58[i].toInt() and 0xFF
|
||||
val temp = remainder * 58 + digit58
|
||||
|
||||
number58[i] = (temp / 256).toByte()
|
||||
|
||||
remainder = temp % 256
|
||||
}
|
||||
|
||||
return remainder.toByte()
|
||||
}
|
||||
}
|
||||
@@ -1,664 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
|
||||
|
||||
/**
|
||||
* Utilities for encoding and decoding the Base64 representation of
|
||||
* binary data. See RFCs <a
|
||||
* href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
|
||||
* href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
|
||||
*/
|
||||
object Base64 {
|
||||
/**
|
||||
* Encoder flag bit to omit the padding '=' characters at the end
|
||||
* of the output (if any).
|
||||
*/
|
||||
const val NO_PADDING = 1
|
||||
|
||||
/**
|
||||
* Encoder flag bit to omit all line terminators (i.e., the output
|
||||
* will be on one long line).
|
||||
*/
|
||||
const val NO_WRAP = 2
|
||||
|
||||
/**
|
||||
* Encoder flag bit to indicate lines should be terminated with a
|
||||
* CRLF pair instead of just an LF. Has no effect if `NO_WRAP` is specified as well.
|
||||
*/
|
||||
const val CRLF = 4
|
||||
|
||||
/**
|
||||
* Encoder/decoder flag bit to indicate using the "URL and
|
||||
* filename safe" variant of Base64 (see RFC 3548 section 4) where
|
||||
* `-` and `_` are used in place of `+` and
|
||||
* `/`.
|
||||
*/
|
||||
const val URL_SAFE = 8
|
||||
|
||||
/**
|
||||
* Default values for encoder/decoder flags.
|
||||
*/
|
||||
const val DEFAULT = NO_WRAP
|
||||
|
||||
|
||||
// --------------------------------------------------------
|
||||
// decoding
|
||||
// --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Decode the Base64-encoded data in input and return the data in
|
||||
* a new byte array.
|
||||
|
||||
*
|
||||
* The padding '=' characters at the end are considered optional, but
|
||||
* if any are present, there must be the correct number of them.
|
||||
|
||||
* @param str the input String to decode, which is converted to
|
||||
* * bytes using the default charset
|
||||
* *
|
||||
* @param flags controls certain features of the decoded output.
|
||||
* * Pass `DEFAULT` to decode standard Base64.
|
||||
* *
|
||||
* *
|
||||
* @throws IllegalArgumentException if the input contains
|
||||
* * incorrect padding
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(str: String, flags: Int = DEFAULT): ByteArray {
|
||||
val input = str.toByteArray(Charsets.US_ASCII)
|
||||
return decode(input, 0, input.size, flags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the Base64-encoded data in input and return the data in
|
||||
* a new byte array.
|
||||
|
||||
*
|
||||
* The padding '=' characters at the end are considered optional, but
|
||||
* if any are present, there must be the correct number of them.
|
||||
|
||||
* @param input the data to decode
|
||||
* *
|
||||
* @param offset the position within the input array at which to start
|
||||
* *
|
||||
* @param len the number of bytes of input to decode
|
||||
* *
|
||||
* @param flags controls certain features of the decoded output.
|
||||
* * Pass `DEFAULT` to decode standard Base64.
|
||||
* *
|
||||
* *
|
||||
* @throws IllegalArgumentException if the input contains
|
||||
* * incorrect padding
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(input: ByteArray, offset: Int, len: Int, flags: Int = DEFAULT): ByteArray {
|
||||
// Allocate space for the most data the input could represent.
|
||||
// (It could contain less if it contains whitespace, etc.)
|
||||
val decoder = Decoder(Options(flags), ByteArray(len * 3 / 4))
|
||||
|
||||
if (!decoder.process(input, offset, len, true)) {
|
||||
throw IllegalArgumentException("bad base-64")
|
||||
}
|
||||
|
||||
// Maybe we got lucky and allocated exactly enough output space.
|
||||
if (decoder.op == decoder.output.size) {
|
||||
return decoder.output
|
||||
}
|
||||
|
||||
// Need to shorten the array, so allocate a new one of the
|
||||
// right size and copy.
|
||||
val temp = ByteArray(decoder.op)
|
||||
System.arraycopy(decoder.output, 0, temp, 0, decoder.op)
|
||||
return temp
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// encoding
|
||||
// --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Base64-encode the given data and return a newly allocated
|
||||
* String with the result.
|
||||
|
||||
* @param input the data to encode
|
||||
* *
|
||||
* @param flags controls certain features of the encoded output.
|
||||
* * Passing `DEFAULT` results in output that
|
||||
* * adheres to RFC 2045.
|
||||
*/
|
||||
fun encodeToString(input: ByteArray, flags: Int = DEFAULT): String {
|
||||
return String(encode(input, 0, input.size, flags), Charsets.US_ASCII)
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-encode the given data and return a newly allocated
|
||||
* byte[] with the result.
|
||||
|
||||
* @param input the data to encode
|
||||
* *
|
||||
* @param offset the position within the input array at which to
|
||||
* * start
|
||||
* *
|
||||
* @param len the number of bytes of input to encode
|
||||
* *
|
||||
* @param flags controls certain features of the encoded output.
|
||||
* * Passing `DEFAULT` results in output that
|
||||
* * adheres to RFC 2045.
|
||||
*/
|
||||
fun encode(input: ByteArray, offset: Int, len: Int, flags: Int): ByteArray {
|
||||
// Compute the exact length of the array we will produce.
|
||||
var output_len = len / 3 * 4
|
||||
|
||||
val options = Options(flags)
|
||||
|
||||
// Account for the tail of the data and the padding bytes, if any.
|
||||
if (options.do_padding) {
|
||||
if (len % 3 > 0) {
|
||||
output_len += 4
|
||||
}
|
||||
} else {
|
||||
when (len % 3) {
|
||||
0 -> {
|
||||
}
|
||||
1 -> output_len += 2
|
||||
2 -> output_len += 3
|
||||
}
|
||||
}
|
||||
|
||||
// Account for the newlines, if any.
|
||||
if (options.do_newline && len > 0) {
|
||||
output_len += ((len - 1) / (3 * Encoder.LINE_GROUPS) + 1) * if (options.do_cr) 2 else 1
|
||||
}
|
||||
|
||||
val encoder = Encoder(options, ByteArray(output_len))
|
||||
encoder.process(input, offset, len, true)
|
||||
|
||||
assert(encoder.op == output_len)
|
||||
|
||||
return encoder.output
|
||||
}
|
||||
}
|
||||
|
||||
class Options(flags: Int) {
|
||||
val do_padding = flags and Base64.NO_PADDING == 0
|
||||
val do_newline = flags and Base64.NO_WRAP == 0
|
||||
val do_cr = flags and Base64.CRLF != 0
|
||||
val url_safe = flags and Base64.URL_SAFE == 0
|
||||
}
|
||||
|
||||
private abstract class Coder(val output: ByteArray) {
|
||||
var op = 0
|
||||
|
||||
/**
|
||||
* Encode/decode another block of input data. this.output is
|
||||
* provided by the caller, and must be big enough to hold all
|
||||
* the coded data. On exit, this.opwill be set to the length
|
||||
* of the coded data.
|
||||
*
|
||||
* @param finish true if this is the final call to process for
|
||||
* this object. Will finalize the coder state and
|
||||
* include any final bytes in the output.
|
||||
*
|
||||
* @return true if the input so far is good; false if some
|
||||
* error has been detected in the input stream..
|
||||
*/
|
||||
abstract fun process(input: ByteArray, offset: Int, len: Int, finish: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* @return the maximum number of bytes a call to process()
|
||||
* could produce for the given number of input bytes. This may
|
||||
* be an overestimate.
|
||||
*/
|
||||
abstract fun maxOutputSize(len: Int): Int
|
||||
}
|
||||
|
||||
private class Decoder(options: Options, output: ByteArray) : Coder(output) {
|
||||
|
||||
/**
|
||||
* States 0-3 are reading through the next input tuple.
|
||||
* State 4 is having read one '=' and expecting exactly
|
||||
* one more.
|
||||
* State 5 is expecting no more data or padding characters
|
||||
* in the input.
|
||||
* State 6 is the error state; an error has been detected
|
||||
* in the input and no future input can "fix" it.
|
||||
*/
|
||||
private var state: Int = 0 // state number (0 to 6)
|
||||
private var value: Int = 0
|
||||
|
||||
private val alphabet = if (options.url_safe) DECODE else DECODE_WEBSAFE
|
||||
|
||||
/**
|
||||
* @return an overestimate for the number of bytes `len` bytes could decode to.
|
||||
*/
|
||||
override fun maxOutputSize(len: Int): Int {
|
||||
return len * 3 / 4 + 10
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode another block of input data.
|
||||
|
||||
* @return true if the state machine is still healthy. false if
|
||||
* * bad base-64 data has been detected in the input stream.
|
||||
*/
|
||||
override fun process(input: ByteArray, offset: Int, len: Int, finish: Boolean): Boolean {
|
||||
var end = len
|
||||
if (this.state == 6) return false
|
||||
|
||||
var p = offset
|
||||
end += offset
|
||||
|
||||
// Using local variables makes the decoder about 12%
|
||||
// faster than if we manipulate the member variables in
|
||||
// the loop. (Even alphabet makes a measurable
|
||||
// difference, which is somewhat surprising to me since
|
||||
// the member variable is final.)
|
||||
var state = this.state
|
||||
var value = this.value
|
||||
var op = 0
|
||||
val output = this.output
|
||||
val alphabet = this.alphabet
|
||||
|
||||
while (p < end) {
|
||||
// Try the fast path: we're starting a new tuple and the
|
||||
// next four bytes of the input stream are all data
|
||||
// bytes. This corresponds to going through states
|
||||
// 0-1-2-3-0. We expect to use this method for most of
|
||||
// the data.
|
||||
//
|
||||
// If any of the next four bytes of input are non-data
|
||||
// (whitespace, etc.), value will end up negative. (All
|
||||
// the non-data values in decode are small negative
|
||||
// numbers, so shifting any of them up and or'ing them
|
||||
// together will result in a value with its top bit set.)
|
||||
//
|
||||
// You can remove this whole block and the output should
|
||||
// be the same, just slower.
|
||||
if (state == 0) {
|
||||
fun nextVal(): Int {
|
||||
value = alphabet[input[p].toInt() and 0xff] shl 18 or
|
||||
(alphabet[input[p + 1].toInt() and 0xff] shl 12) or
|
||||
(alphabet[input[p + 2].toInt() and 0xff] shl 6) or
|
||||
alphabet[input[p + 3].toInt() and 0xff]
|
||||
return value
|
||||
}
|
||||
while (p + 4 <= end && nextVal() >= 0) {
|
||||
output[op + 2] = value.toByte()
|
||||
output[op + 1] = (value shr 8).toByte()
|
||||
output[op] = (value shr 16).toByte()
|
||||
op += 3
|
||||
p += 4
|
||||
}
|
||||
if (p >= end) break
|
||||
}
|
||||
|
||||
// The fast path isn't available -- either we've read a
|
||||
// partial tuple, or the next four input bytes aren't all
|
||||
// data, or whatever. Fall back to the slower state
|
||||
// machine implementation.
|
||||
|
||||
val d = alphabet[input[p++].toInt() and 0xff]
|
||||
|
||||
when (state) {
|
||||
0 -> if (d >= 0) {
|
||||
value = d
|
||||
++state
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
|
||||
1 -> if (d >= 0) {
|
||||
value = value shl 6 or d
|
||||
++state
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
|
||||
2 -> if (d >= 0) {
|
||||
value = value shl 6 or d
|
||||
++state
|
||||
} else if (d == EQUALS) {
|
||||
// Emit the last (partial) output tuple;
|
||||
// expect exactly one more padding character.
|
||||
output[op++] = (value shr 4).toByte()
|
||||
state = 4
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
|
||||
3 -> if (d >= 0) {
|
||||
// Emit the output triple and return to state 0.
|
||||
value = value shl 6 or d
|
||||
output[op + 2] = value.toByte()
|
||||
output[op + 1] = (value shr 8).toByte()
|
||||
output[op] = (value shr 16).toByte()
|
||||
op += 3
|
||||
state = 0
|
||||
} else if (d == EQUALS) {
|
||||
// Emit the last (partial) output tuple;
|
||||
// expect no further data or padding characters.
|
||||
output[op + 1] = (value shr 2).toByte()
|
||||
output[op] = (value shr 10).toByte()
|
||||
op += 2
|
||||
state = 5
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
|
||||
4 -> if (d == EQUALS) {
|
||||
++state
|
||||
} else if (d != SKIP) {
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
|
||||
5 -> if (d != SKIP) {
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!finish) {
|
||||
// We're out of input, but a future call could provide
|
||||
// more.
|
||||
this.state = state
|
||||
this.value = value
|
||||
this.op = op
|
||||
return true
|
||||
}
|
||||
|
||||
// Done reading input. Now figure out where we are left in
|
||||
// the state machine and finish up.
|
||||
|
||||
when (state) {
|
||||
0 -> {
|
||||
}
|
||||
1 -> {
|
||||
// Read one extra input byte, which isn't enough to
|
||||
// make another output byte. Illegal.
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
2 ->
|
||||
// Read two extra input bytes, enough to emit 1 more
|
||||
// output byte. Fine.
|
||||
output[op++] = (value shr 4).toByte()
|
||||
3 -> {
|
||||
// Read three extra input bytes, enough to emit 2 more
|
||||
// output bytes. Fine.
|
||||
output[op++] = (value shr 10).toByte()
|
||||
output[op++] = (value shr 2).toByte()
|
||||
}
|
||||
4 -> {
|
||||
// Read one padding '=' when we expected 2. Illegal.
|
||||
this.state = 6
|
||||
return false
|
||||
}
|
||||
5 -> {
|
||||
}
|
||||
}// Output length is a multiple of three. Fine.
|
||||
// Read all the padding '='s we expected and no more.
|
||||
// Fine.
|
||||
|
||||
this.state = state
|
||||
this.op = op
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Lookup table for turning bytes into their position in the
|
||||
* Base64 alphabet.
|
||||
*/
|
||||
private val DECODE = intArrayOf(
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
|
||||
)
|
||||
|
||||
/**
|
||||
* Decode lookup table for the "web safe" variant (RFC 3548
|
||||
* sec. 4) where - and _ replace + and /.
|
||||
*/
|
||||
private val DECODE_WEBSAFE = intArrayOf(
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
|
||||
)
|
||||
|
||||
/** Non-data values in the DECODE arrays. */
|
||||
private val SKIP = -1
|
||||
private val EQUALS = -2
|
||||
}
|
||||
}
|
||||
|
||||
private class Encoder(val options: Options, output: ByteArray) : Coder(output) {
|
||||
|
||||
private val alphabet: ByteArray = if (options.url_safe) ENCODE else ENCODE_WEBSAFE
|
||||
|
||||
private val tail = ByteArray(2)
|
||||
private var tailLen = 0
|
||||
private var count = if (options.do_newline) LINE_GROUPS else -1
|
||||
|
||||
/**
|
||||
* @return an overestimate for the number of bytes `len` bytes could encode to.
|
||||
*/
|
||||
override fun maxOutputSize(len: Int): Int {
|
||||
return len * 8 / 5 + 10
|
||||
}
|
||||
|
||||
override fun process(input: ByteArray, offset: Int, len: Int, finish: Boolean): Boolean {
|
||||
var end = len
|
||||
// Using local variables makes the encoder about 9% faster.
|
||||
val alphabet = this.alphabet
|
||||
val output = this.output
|
||||
var op = 0
|
||||
var count = this.count
|
||||
|
||||
var p = offset
|
||||
end += offset
|
||||
var v = -1
|
||||
|
||||
// First we need to concatenate the tail of the previous call
|
||||
// with any input bytes available now and see if we can empty
|
||||
// the tail.
|
||||
|
||||
when (tailLen) {
|
||||
0 -> {
|
||||
}
|
||||
|
||||
1 -> {
|
||||
if (p + 2 <= end) {
|
||||
// A 1-byte tail with at least 2 bytes of
|
||||
// input available now.
|
||||
v = tail[0].toInt() and 0xff shl 16 or
|
||||
(input[p++].toInt() and 0xff shl 8) or
|
||||
(input[p++].toInt() and 0xff)
|
||||
tailLen = 0
|
||||
}
|
||||
}
|
||||
|
||||
2 -> if (p + 1 <= end) {
|
||||
// A 2-byte tail with at least 1 byte of input.
|
||||
v = tail[0].toInt() and 0xff shl 16 or
|
||||
(tail[1].toInt() and 0xff shl 8) or
|
||||
(input[p++].toInt() and 0xff)
|
||||
tailLen = 0
|
||||
}
|
||||
}// There was no tail.
|
||||
|
||||
if (v != -1) {
|
||||
output[op++] = alphabet[v shr 18 and 0x3f]
|
||||
output[op++] = alphabet[v shr 12 and 0x3f]
|
||||
output[op++] = alphabet[v shr 6 and 0x3f]
|
||||
output[op++] = alphabet[v and 0x3f]
|
||||
if (--count == 0) {
|
||||
if (options.do_cr) output[op++] = CR
|
||||
output[op++] = NL
|
||||
count = LINE_GROUPS
|
||||
}
|
||||
}
|
||||
|
||||
// At this point either there is no tail, or there are fewer
|
||||
// than 3 bytes of input available.
|
||||
|
||||
// The main loop, turning 3 input bytes into 4 output bytes on
|
||||
// each iteration.
|
||||
while (p + 3 <= end) {
|
||||
v = input[p].toInt() and 0xff shl 16 or
|
||||
(input[p + 1].toInt() and 0xff shl 8) or
|
||||
(input[p + 2].toInt() and 0xff)
|
||||
output[op] = alphabet[v shr 18 and 0x3f]
|
||||
output[op + 1] = alphabet[v shr 12 and 0x3f]
|
||||
output[op + 2] = alphabet[v shr 6 and 0x3f]
|
||||
output[op + 3] = alphabet[v and 0x3f]
|
||||
p += 3
|
||||
op += 4
|
||||
if (--count == 0) {
|
||||
if (options.do_cr) output[op++] = CR
|
||||
output[op++] = NL
|
||||
count = LINE_GROUPS
|
||||
}
|
||||
}
|
||||
|
||||
if (finish) {
|
||||
// Finish up the tail of the input. Note that we need to
|
||||
// consume any bytes in tail before any bytes
|
||||
// remaining in input; there should be at most two bytes
|
||||
// total.
|
||||
|
||||
if (p - tailLen == end - 1) {
|
||||
var t = 0
|
||||
v = (if (tailLen > 0) tail[t++] else input[p++]).toInt() and 0xff shl 4
|
||||
tailLen -= t
|
||||
output[op++] = alphabet[v shr 6 and 0x3f]
|
||||
output[op++] = alphabet[v and 0x3f]
|
||||
if (options.do_padding) {
|
||||
output[op++] = PAD
|
||||
output[op++] = PAD
|
||||
}
|
||||
if (options.do_newline) {
|
||||
if (options.do_cr) output[op++] = CR
|
||||
output[op++] = NL
|
||||
}
|
||||
} else if (p - tailLen == end - 2) {
|
||||
var t = 0
|
||||
v = (if (tailLen > 1) tail[t++] else input[p++]).toInt() and 0xff shl 10 or ((if (tailLen > 0) tail[t++] else input[p++]).toInt() and 0xff shl 2)
|
||||
tailLen -= t
|
||||
output[op++] = alphabet[v shr 12 and 0x3f]
|
||||
output[op++] = alphabet[v shr 6 and 0x3f]
|
||||
output[op++] = alphabet[v and 0x3f]
|
||||
if (options.do_padding) {
|
||||
output[op++] = PAD
|
||||
}
|
||||
if (options.do_newline) {
|
||||
if (options.do_cr) output[op++] = CR
|
||||
output[op++] = NL
|
||||
}
|
||||
} else if (options.do_newline && op > 0 && count != LINE_GROUPS) {
|
||||
if (options.do_cr) output[op++] = CR
|
||||
output[op++] = NL
|
||||
}
|
||||
|
||||
assert(tailLen == 0)
|
||||
assert(p == end)
|
||||
} else {
|
||||
// Save the leftovers in tail to be consumed on the next
|
||||
// call to encodeInternal.
|
||||
|
||||
if (p == end - 1) {
|
||||
tail[tailLen++] = input[p]
|
||||
} else if (p == end - 2) {
|
||||
tail[tailLen++] = input[p]
|
||||
tail[tailLen++] = input[p + 1]
|
||||
}
|
||||
}
|
||||
|
||||
this.op = op
|
||||
this.count = count
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PAD = '='.toByte()
|
||||
private const val CR = '\r'.toByte()
|
||||
private const val NL = '\n'.toByte()
|
||||
/**
|
||||
* Emit a new line every this many output tuples. Corresponds to
|
||||
* a 76-character line length (the maximum allowable according to
|
||||
* [RFC 2045](http://www.ietf.org/rfc/rfc2045.txt)).
|
||||
*/
|
||||
val LINE_GROUPS = 19
|
||||
|
||||
/**
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private val ENCODE = charArrayOf(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
||||
).map { it.toByte() }.toByteArray()
|
||||
|
||||
/**
|
||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||
* into output bytes.
|
||||
*/
|
||||
private val ENCODE_WEBSAFE = charArrayOf(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
|
||||
).map { it.toByte() }.toByteArray()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user