Merge branch 'feature/kotlin' into develop
This commit is contained in:
		
							
								
								
									
										55
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -1,22 +1,38 @@ | |||||||
|  | buildscript { | ||||||
|  |     ext.kotlin_version = '1.1.3' | ||||||
|  |     repositories { | ||||||
|  |         mavenCentral() | ||||||
|  |     } | ||||||
|  |     dependencies { | ||||||
|  |         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||||
|  |     } | ||||||
|  | } | ||||||
| plugins { | plugins { | ||||||
|     id 'com.github.ben-manes.versions' version '0.14.0' |     id 'com.github.ben-manes.versions' version '0.15.0' | ||||||
|  |     id "io.spring.dependency-management" version "1.0.3.RELEASE" | ||||||
| } | } | ||||||
|  |  | ||||||
| subprojects { | subprojects { | ||||||
|     apply plugin: 'java' |     apply plugin: 'kotlin' | ||||||
|     apply plugin: 'maven' |     apply plugin: 'maven' | ||||||
|     apply plugin: 'signing' |     apply plugin: 'signing' | ||||||
|     apply plugin: 'jacoco' |     apply plugin: 'jacoco' | ||||||
|     apply plugin: 'gitflow-version' |     apply plugin: 'gitflow-version' | ||||||
|  |     apply plugin: 'io.spring.dependency-management' | ||||||
|     apply plugin: 'com.github.ben-manes.versions' |     apply plugin: 'com.github.ben-manes.versions' | ||||||
|  |  | ||||||
|     sourceCompatibility = 1.7 |     sourceCompatibility = 1.7 | ||||||
|  |     targetCompatibility = 1.7 | ||||||
|     group = 'ch.dissem.jabit' |     group = 'ch.dissem.jabit' | ||||||
|  |  | ||||||
|     repositories { |     repositories { | ||||||
|         mavenCentral() |         mavenCentral() | ||||||
|         maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } |         maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } | ||||||
|     } |     } | ||||||
|  |     dependencies { | ||||||
|  |         compile "org.jetbrains.kotlin:kotlin-stdlib-jre7" | ||||||
|  |         compile "org.jetbrains.kotlin:kotlin-reflect" | ||||||
|  |     } | ||||||
|  |  | ||||||
|     test { |     test { | ||||||
|         testLogging { |         testLogging { | ||||||
| @@ -38,6 +54,14 @@ subprojects { | |||||||
|         archives javadocJar, sourcesJar |         archives javadocJar, sourcesJar | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     jar { | ||||||
|  |         manifest { | ||||||
|  |             attributes 'Implementation-Title': "Jabit ${project.name.capitalize()}", | ||||||
|  |                        'Implementation-Version': version | ||||||
|  |         } | ||||||
|  |         baseName "jabit-${project.name}" | ||||||
|  |     } | ||||||
|  |  | ||||||
|     signing { |     signing { | ||||||
|         required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 } |         required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 } | ||||||
|         sign configurations.archives |         sign configurations.archives | ||||||
| @@ -93,4 +117,31 @@ subprojects { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     check.dependsOn jacocoTestReport |     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:1.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 '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' | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,10 +24,28 @@ artifacts { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     compile 'org.slf4j:slf4j-api:1.7.25' |     compile 'org.slf4j:slf4j-api' | ||||||
|     compile 'ch.dissem.msgpack:msgpack:1.0.0' |     compile 'ch.dissem.msgpack:msgpack:1.0.0' | ||||||
|     testCompile 'junit:junit:4.12' |     testCompile 'junit:junit:4.12' | ||||||
|     testCompile 'org.hamcrest:hamcrest-library:1.3' |     testCompile 'org.hamcrest:hamcrest-library:1.3' | ||||||
|     testCompile 'org.mockito:mockito-core:2.7.21' |     testCompile 'com.nhaarman:mockito-kotlin:1.5.0' | ||||||
|     testCompile project(':cryptography-bc') |     testCompile project(':cryptography-bc') | ||||||
| } | } | ||||||
|  |  | ||||||
|  | def generatedResources = "${project.buildDir}/generated-resources/main" | ||||||
|  |  | ||||||
|  | sourceSets { | ||||||
|  |     main { | ||||||
|  |         output.dir(generatedResources, builtBy: 'generateVersionInfo') | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | task('generateVersionInfo') { | ||||||
|  |     doLast { | ||||||
|  |         def dir = new File(generatedResources) | ||||||
|  |         if (!dir.exists()) { | ||||||
|  |             dir.mkdirs() | ||||||
|  |         } | ||||||
|  |         def file = new File(generatedResources, "version") | ||||||
|  |         file.write(project.version.toString()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,437 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.*; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Broadcast; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.ObjectType; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.PrivateKey; |  | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; |  | ||||||
| import ch.dissem.bitmessage.factory.Factory; |  | ||||||
| import ch.dissem.bitmessage.ports.*; |  | ||||||
| import ch.dissem.bitmessage.utils.Property; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.net.InetAddress; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.concurrent.CancellationException; |  | ||||||
| import java.util.concurrent.ExecutionException; |  | ||||||
| import java.util.concurrent.Future; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; |  | ||||||
| import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; |  | ||||||
| import static ch.dissem.bitmessage.utils.UnixTime.HOUR; |  | ||||||
| import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * <p>Use this class if you want to create a Bitmessage client.</p> |  | ||||||
|  * You'll need the Builder to create a BitmessageContext, and set the following properties: |  | ||||||
|  * <ul> |  | ||||||
|  * <li>addressRepo</li> |  | ||||||
|  * <li>inventory</li> |  | ||||||
|  * <li>nodeRegistry</li> |  | ||||||
|  * <li>networkHandler</li> |  | ||||||
|  * <li>messageRepo</li> |  | ||||||
|  * <li>streams</li> |  | ||||||
|  * </ul> |  | ||||||
|  * <p>The default implementations in the different module builds can be used.</p> |  | ||||||
|  * <p>The port defaults to 8444 (the default Bitmessage port)</p> |  | ||||||
|  */ |  | ||||||
| public class BitmessageContext { |  | ||||||
|     public static final int CURRENT_VERSION = 3; |  | ||||||
|     private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class); |  | ||||||
|  |  | ||||||
|     private final InternalContext ctx; |  | ||||||
|  |  | ||||||
|     private final Labeler labeler; |  | ||||||
|  |  | ||||||
|     private final boolean sendPubkeyOnIdentityCreation; |  | ||||||
|  |  | ||||||
|     private BitmessageContext(Builder builder) { |  | ||||||
|         ctx = new InternalContext(builder); |  | ||||||
|         labeler = builder.labeler; |  | ||||||
|         ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable |  | ||||||
|         sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation; |  | ||||||
|         if (builder.listener instanceof Listener.WithContext) { |  | ||||||
|             ((Listener.WithContext) builder.listener).setContext(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public AddressRepository addresses() { |  | ||||||
|         return ctx.getAddressRepository(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public MessageRepository messages() { |  | ||||||
|         return ctx.getMessageRepository(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Labeler labeler() { |  | ||||||
|         return labeler; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress createIdentity(boolean shorter, Feature... features) { |  | ||||||
|         final BitmessageAddress identity = new BitmessageAddress(new PrivateKey( |  | ||||||
|             shorter, |  | ||||||
|             ctx.getStreams()[0], |  | ||||||
|             NETWORK_NONCE_TRIALS_PER_BYTE, |  | ||||||
|             NETWORK_EXTRA_BYTES, |  | ||||||
|             features |  | ||||||
|         )); |  | ||||||
|         ctx.getAddressRepository().save(identity); |  | ||||||
|         if (sendPubkeyOnIdentityCreation) { |  | ||||||
|             ctx.sendPubkey(identity, identity.getStream()); |  | ||||||
|         } |  | ||||||
|         return identity; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress joinChan(String passphrase, String address) { |  | ||||||
|         BitmessageAddress chan = BitmessageAddress.chan(address, passphrase); |  | ||||||
|         chan.setAlias(passphrase); |  | ||||||
|         ctx.getAddressRepository().save(chan); |  | ||||||
|         return chan; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress createChan(String passphrase) { |  | ||||||
|         // FIXME: hardcoded stream number |  | ||||||
|         BitmessageAddress chan = BitmessageAddress.chan(1, passphrase); |  | ||||||
|         ctx.getAddressRepository().save(chan); |  | ||||||
|         return chan; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<BitmessageAddress> createDeterministicAddresses( |  | ||||||
|         String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { |  | ||||||
|         List<BitmessageAddress> result = BitmessageAddress.deterministic( |  | ||||||
|             passphrase, numberOfAddresses, version, stream, shorter); |  | ||||||
|         for (int i = 0; i < result.size(); i++) { |  | ||||||
|             BitmessageAddress address = result.get(i); |  | ||||||
|             address.setAlias("deterministic (" + (i + 1) + ")"); |  | ||||||
|             ctx.getAddressRepository().save(address); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void broadcast(final BitmessageAddress from, final String subject, final String message) { |  | ||||||
|         Plaintext msg = new Plaintext.Builder(BROADCAST) |  | ||||||
|             .from(from) |  | ||||||
|             .message(subject, message) |  | ||||||
|             .build(); |  | ||||||
|         send(msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) { |  | ||||||
|         if (from.getPrivateKey() == null) { |  | ||||||
|             throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); |  | ||||||
|         } |  | ||||||
|         Plaintext msg = new Plaintext.Builder(MSG) |  | ||||||
|             .from(from) |  | ||||||
|             .to(to) |  | ||||||
|             .message(subject, message) |  | ||||||
|             .build(); |  | ||||||
|         send(msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void send(final Plaintext msg) { |  | ||||||
|         if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) { |  | ||||||
|             throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); |  | ||||||
|         } |  | ||||||
|         labeler().markAsSending(msg); |  | ||||||
|         BitmessageAddress to = msg.getTo(); |  | ||||||
|         if (to != null) { |  | ||||||
|             if (to.getPubkey() == null) { |  | ||||||
|                 LOG.info("Public key is missing from recipient. Requesting."); |  | ||||||
|                 ctx.requestPubkey(to); |  | ||||||
|             } |  | ||||||
|             if (to.getPubkey() == null) { |  | ||||||
|                 ctx.getMessageRepository().save(msg); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (to == null || to.getPubkey() != null) { |  | ||||||
|             LOG.info("Sending message."); |  | ||||||
|             ctx.getMessageRepository().save(msg); |  | ||||||
|             if (msg.getType() == MSG) { |  | ||||||
|                 ctx.send(msg); |  | ||||||
|             } else { |  | ||||||
|                 ctx.send( |  | ||||||
|                     msg.getFrom(), |  | ||||||
|                     to, |  | ||||||
|                     Factory.getBroadcast(msg), |  | ||||||
|                     msg.getTTL() |  | ||||||
|                 ); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void startup() { |  | ||||||
|         ctx.getNetworkHandler().start(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void shutdown() { |  | ||||||
|         ctx.getNetworkHandler().stop(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @param host             a trusted node that must be reliable (it's used for every synchronization) |  | ||||||
|      * @param port             of the trusted host, default is 8444 |  | ||||||
|      * @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even |  | ||||||
|      *                         if not all objects were fetched |  | ||||||
|      * @param wait             waits for the synchronization thread to finish |  | ||||||
|      */ |  | ||||||
|     public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) { |  | ||||||
|         Future<?> future = ctx.getNetworkHandler().synchronize(host, port, timeoutInSeconds); |  | ||||||
|         if (wait) { |  | ||||||
|             try { |  | ||||||
|                 future.get(); |  | ||||||
|             } catch (InterruptedException e) { |  | ||||||
|                 LOG.info("Thread was interrupted. Trying to shut down synchronization and returning."); |  | ||||||
|                 future.cancel(true); |  | ||||||
|             } catch (CancellationException | ExecutionException e) { |  | ||||||
|                 LOG.debug(e.getMessage(), e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Send a custom message to a specific node (that should implement handling for this message type) and returns |  | ||||||
|      * the response, which in turn is expected to be a {@link CustomMessage}. |  | ||||||
|      * |  | ||||||
|      * @param server  the node's address |  | ||||||
|      * @param port    the node's port |  | ||||||
|      * @param request the request |  | ||||||
|      * @return the response |  | ||||||
|      */ |  | ||||||
|     public CustomMessage send(InetAddress server, int port, CustomMessage request) { |  | ||||||
|         return ctx.getNetworkHandler().send(server, port, request); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Removes expired objects from the inventory. You should call this method regularly, |  | ||||||
|      * e.g. daily and on each shutdown. |  | ||||||
|      */ |  | ||||||
|     public void cleanup() { |  | ||||||
|         ctx.getInventory().cleanup(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Sends messages again whose time to live expired without being acknowledged. (And whose |  | ||||||
|      * recipient is expected to send acknowledgements. |  | ||||||
|      * <p> |  | ||||||
|      * You should call this method regularly, but be aware of the following: |  | ||||||
|      * <ul> |  | ||||||
|      * <li>As messages might be sent, POW will be done. It is therefore not advised to |  | ||||||
|      * call it on shutdown.</li> |  | ||||||
|      * <li>It shouldn't be called right after startup, as it's possible the missing |  | ||||||
|      * acknowledgement was sent while the client was offline.</li> |  | ||||||
|      * <li>Other than that, the call isn't expensive as long as there is no message |  | ||||||
|      * to send, so it might be a good idea to just call it every few minutes.</li> |  | ||||||
|      * </ul> |  | ||||||
|      */ |  | ||||||
|     public void resendUnacknowledgedMessages() { |  | ||||||
|         ctx.resendUnacknowledged(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isRunning() { |  | ||||||
|         return ctx.getNetworkHandler().isRunning(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void addContact(BitmessageAddress contact) { |  | ||||||
|         ctx.getAddressRepository().save(contact); |  | ||||||
|         if (contact.getPubkey() == null) { |  | ||||||
|             BitmessageAddress stored = ctx.getAddressRepository().getAddress(contact.getAddress()); |  | ||||||
|             if (stored.getPubkey() == null) { |  | ||||||
|                 ctx.requestPubkey(contact); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void addSubscribtion(BitmessageAddress address) { |  | ||||||
|         address.setSubscribed(true); |  | ||||||
|         ctx.getAddressRepository().save(address); |  | ||||||
|         tryToFindBroadcastsForAddress(address); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void tryToFindBroadcastsForAddress(BitmessageAddress address) { |  | ||||||
|         for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) { |  | ||||||
|             try { |  | ||||||
|                 Broadcast broadcast = (Broadcast) object.getPayload(); |  | ||||||
|                 broadcast.decrypt(address); |  | ||||||
|                 // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with |  | ||||||
|                 // other subscriptions and the interface stays as simple as possible. |  | ||||||
|                 ctx.getNetworkListener().receive(object); |  | ||||||
|             } catch (DecryptionFailedException ignore) { |  | ||||||
|             } catch (Exception e) { |  | ||||||
|                 LOG.debug(e.getMessage(), e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Property status() { |  | ||||||
|         return new Property("status", null, |  | ||||||
|             ctx.getNetworkHandler().getNetworkStatus(), |  | ||||||
|             new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size()) |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Returns the {@link InternalContext} - normally you wouldn't need it, |  | ||||||
|      * unless you are doing something crazy with the protocol. |  | ||||||
|      */ |  | ||||||
|     public InternalContext internals() { |  | ||||||
|         return ctx; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public interface Listener { |  | ||||||
|         void receive(Plaintext plaintext); |  | ||||||
|  |  | ||||||
|         /** |  | ||||||
|          * A message listener that needs a {@link BitmessageContext}, i.e. for implementing some sort of chat bot. |  | ||||||
|          */ |  | ||||||
|         interface WithContext extends Listener { |  | ||||||
|             void setContext(BitmessageContext ctx); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         int port = 8444; |  | ||||||
|         Inventory inventory; |  | ||||||
|         NodeRegistry nodeRegistry; |  | ||||||
|         NetworkHandler networkHandler; |  | ||||||
|         AddressRepository addressRepo; |  | ||||||
|         MessageRepository messageRepo; |  | ||||||
|         ProofOfWorkRepository proofOfWorkRepository; |  | ||||||
|         ProofOfWorkEngine proofOfWorkEngine; |  | ||||||
|         Cryptography cryptography; |  | ||||||
|         CustomCommandHandler customCommandHandler; |  | ||||||
|         Labeler labeler; |  | ||||||
|         Listener listener; |  | ||||||
|         int connectionLimit = 150; |  | ||||||
|         long connectionTTL = 30 * MINUTE; |  | ||||||
|         boolean sendPubkeyOnIdentityCreation = true; |  | ||||||
|  |  | ||||||
|         public Builder port(int port) { |  | ||||||
|             this.port = port; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder inventory(Inventory inventory) { |  | ||||||
|             this.inventory = inventory; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder nodeRegistry(NodeRegistry nodeRegistry) { |  | ||||||
|             this.nodeRegistry = nodeRegistry; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder networkHandler(NetworkHandler networkHandler) { |  | ||||||
|             this.networkHandler = networkHandler; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addressRepo(AddressRepository addressRepo) { |  | ||||||
|             this.addressRepo = addressRepo; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder messageRepo(MessageRepository messageRepo) { |  | ||||||
|             this.messageRepo = messageRepo; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) { |  | ||||||
|             this.proofOfWorkRepository = proofOfWorkRepository; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder cryptography(Cryptography cryptography) { |  | ||||||
|             this.cryptography = cryptography; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder customCommandHandler(CustomCommandHandler handler) { |  | ||||||
|             this.customCommandHandler = handler; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) { |  | ||||||
|             this.proofOfWorkEngine = proofOfWorkEngine; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder labeler(Labeler labeler) { |  | ||||||
|             this.labeler = labeler; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder listener(Listener listener) { |  | ||||||
|             this.listener = listener; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder connectionLimit(int connectionLimit) { |  | ||||||
|             this.connectionLimit = connectionLimit; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder connectionTTL(int hours) { |  | ||||||
|             this.connectionTTL = hours * HOUR; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /** |  | ||||||
|          * By default a client will send the public key when an identity is being created. On weaker devices |  | ||||||
|          * this behaviour might not be desirable. |  | ||||||
|          */ |  | ||||||
|         public Builder doNotSendPubkeyOnIdentityCreation() { |  | ||||||
|             this.sendPubkeyOnIdentityCreation = false; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public BitmessageContext build() { |  | ||||||
|             nonNull("inventory", inventory); |  | ||||||
|             nonNull("nodeRegistry", nodeRegistry); |  | ||||||
|             nonNull("networkHandler", networkHandler); |  | ||||||
|             nonNull("addressRepo", addressRepo); |  | ||||||
|             nonNull("messageRepo", messageRepo); |  | ||||||
|             nonNull("proofOfWorkRepo", proofOfWorkRepository); |  | ||||||
|             if (proofOfWorkEngine == null) { |  | ||||||
|                 proofOfWorkEngine = new MultiThreadedPOWEngine(); |  | ||||||
|             } |  | ||||||
|             if (labeler == null) { |  | ||||||
|                 labeler = new DefaultLabeler(); |  | ||||||
|             } |  | ||||||
|             if (customCommandHandler == null) { |  | ||||||
|                 customCommandHandler = new CustomCommandHandler() { |  | ||||||
|                     @Override |  | ||||||
|                     public MessagePayload handle(CustomMessage request) { |  | ||||||
|                         LOG.debug("Received custom request, but no custom command handler configured."); |  | ||||||
|                         return null; |  | ||||||
|                     } |  | ||||||
|                 }; |  | ||||||
|             } |  | ||||||
|             return new BitmessageContext(this); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private void nonNull(String name, Object o) { |  | ||||||
|             if (o == null) throw new IllegalStateException(name + " must not be null"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,191 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.ObjectMessage; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.*; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; |  | ||||||
| import ch.dissem.bitmessage.ports.Labeler; |  | ||||||
| import ch.dissem.bitmessage.ports.NetworkHandler; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED; |  | ||||||
|  |  | ||||||
| class DefaultMessageListener implements NetworkHandler.MessageListener, InternalContext.ContextHolder { |  | ||||||
|     private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class); |  | ||||||
|     private final Labeler labeler; |  | ||||||
|     private final BitmessageContext.Listener listener; |  | ||||||
|     private InternalContext ctx; |  | ||||||
|  |  | ||||||
|     public DefaultMessageListener(Labeler labeler, BitmessageContext.Listener listener) { |  | ||||||
|         this.labeler = labeler; |  | ||||||
|         this.listener = listener; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setContext(InternalContext context) { |  | ||||||
|         this.ctx = context; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     @SuppressWarnings("ConstantConditions") |  | ||||||
|     public void receive(ObjectMessage object) throws IOException { |  | ||||||
|         ObjectPayload payload = object.getPayload(); |  | ||||||
|         if (payload.getType() == null) { |  | ||||||
|             if (payload instanceof GenericPayload) { |  | ||||||
|                 receive((GenericPayload) payload); |  | ||||||
|             } |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         switch (payload.getType()) { |  | ||||||
|             case GET_PUBKEY: { |  | ||||||
|                 receive(object, (GetPubkey) payload); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case PUBKEY: { |  | ||||||
|                 receive(object, (Pubkey) payload); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case MSG: { |  | ||||||
|                 receive(object, (Msg) payload); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             case BROADCAST: { |  | ||||||
|                 receive(object, (Broadcast) payload); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             default: { |  | ||||||
|                 throw new IllegalArgumentException("Unknown payload type " + payload.getType()); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void receive(ObjectMessage object, GetPubkey getPubkey) { |  | ||||||
|         BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag()); |  | ||||||
|         if (identity != null && identity.getPrivateKey() != null && !identity.isChan()) { |  | ||||||
|             LOG.info("Got pubkey request for identity " + identity); |  | ||||||
|             // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days |  | ||||||
|             ctx.sendPubkey(identity, object.getStream()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void receive(ObjectMessage object, Pubkey pubkey) throws IOException { |  | ||||||
|         BitmessageAddress address; |  | ||||||
|         try { |  | ||||||
|             if (pubkey instanceof V4Pubkey) { |  | ||||||
|                 V4Pubkey v4Pubkey = (V4Pubkey) pubkey; |  | ||||||
|                 address = ctx.getAddressRepository().findContact(v4Pubkey.getTag()); |  | ||||||
|                 if (address != null) { |  | ||||||
|                     v4Pubkey.decrypt(address.getPublicDecryptionKey()); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 address = ctx.getAddressRepository().findContact(pubkey.getRipe()); |  | ||||||
|             } |  | ||||||
|             if (address != null && address.getPubkey() == null) { |  | ||||||
|                 updatePubkey(address, pubkey); |  | ||||||
|             } |  | ||||||
|         } catch (DecryptionFailedException ignore) { |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void updatePubkey(BitmessageAddress address, Pubkey pubkey) { |  | ||||||
|         address.setPubkey(pubkey); |  | ||||||
|         LOG.info("Got pubkey for contact " + address); |  | ||||||
|         ctx.getAddressRepository().save(address); |  | ||||||
|         List<Plaintext> messages = ctx.getMessageRepository().findMessages(PUBKEY_REQUESTED, address); |  | ||||||
|         LOG.info("Sending " + messages.size() + " messages for contact " + address); |  | ||||||
|         for (Plaintext msg : messages) { |  | ||||||
|             ctx.getLabeler().markAsSending(msg); |  | ||||||
|             ctx.getMessageRepository().save(msg); |  | ||||||
|             ctx.send(msg); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void receive(ObjectMessage object, Msg msg) throws IOException { |  | ||||||
|         for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) { |  | ||||||
|             try { |  | ||||||
|                 msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); |  | ||||||
|                 Plaintext plaintext = msg.getPlaintext(); |  | ||||||
|                 plaintext.setTo(identity); |  | ||||||
|                 if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) { |  | ||||||
|                     LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); |  | ||||||
|                 } else { |  | ||||||
|                     receive(object.getInventoryVector(), plaintext); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             } catch (DecryptionFailedException ignore) { |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void receive(GenericPayload ack) { |  | ||||||
|         if (ack.getData().length == Msg.ACK_LENGTH) { |  | ||||||
|             Plaintext msg = ctx.getMessageRepository().getMessageForAck(ack.getData()); |  | ||||||
|             if (msg != null) { |  | ||||||
|                 ctx.getLabeler().markAsAcknowledged(msg); |  | ||||||
|                 ctx.getMessageRepository().save(msg); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException { |  | ||||||
|         byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null; |  | ||||||
|         for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) { |  | ||||||
|             if (tag != null && !Arrays.equals(tag, subscription.getTag())) { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             try { |  | ||||||
|                 broadcast.decrypt(subscription.getPublicDecryptionKey()); |  | ||||||
|                 if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) { |  | ||||||
|                     LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); |  | ||||||
|                 } else { |  | ||||||
|                     receive(object.getInventoryVector(), broadcast.getPlaintext()); |  | ||||||
|                 } |  | ||||||
|             } catch (DecryptionFailedException ignore) { |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void receive(InventoryVector iv, Plaintext msg) { |  | ||||||
|         BitmessageAddress contact = ctx.getAddressRepository().getAddress(msg.getFrom().getAddress()); |  | ||||||
|         if (contact != null && contact.getPubkey() == null) { |  | ||||||
|             updatePubkey(contact, msg.getFrom().getPubkey()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         msg.setInventoryVector(iv); |  | ||||||
|         labeler.setLabels(msg); |  | ||||||
|         ctx.getMessageRepository().save(msg); |  | ||||||
|         listener.receive(msg); |  | ||||||
|  |  | ||||||
|         if (msg.getType() == Plaintext.Type.MSG && msg.getTo().has(Pubkey.Feature.DOES_ACK)) { |  | ||||||
|             ObjectMessage ack = msg.getAckMessage(); |  | ||||||
|             if (ack != null) { |  | ||||||
|                 ctx.getInventory().storeObject(ack); |  | ||||||
|                 ctx.getNetworkHandler().offer(ack.getInventoryVector()); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,326 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.*; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.*; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.ports.*; |  | ||||||
| import ch.dissem.bitmessage.utils.Singleton; |  | ||||||
| import ch.dissem.bitmessage.utils.TTL; |  | ||||||
| import ch.dissem.bitmessage.utils.UnixTime; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.TreeSet; |  | ||||||
| import java.util.concurrent.Executor; |  | ||||||
| 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. |  | ||||||
|  * <p> |  | ||||||
|  * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply. |  | ||||||
|  * </p> |  | ||||||
|  */ |  | ||||||
| public class InternalContext { |  | ||||||
|     private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class); |  | ||||||
|  |  | ||||||
|     public final static long NETWORK_NONCE_TRIALS_PER_BYTE = 1000; |  | ||||||
|     public final static long NETWORK_EXTRA_BYTES = 1000; |  | ||||||
|  |  | ||||||
|     private final Executor threadPool = Executors.newCachedThreadPool(); |  | ||||||
|  |  | ||||||
|     private final Cryptography cryptography; |  | ||||||
|     private final Inventory inventory; |  | ||||||
|     private final NodeRegistry nodeRegistry; |  | ||||||
|     private final NetworkHandler networkHandler; |  | ||||||
|     private final AddressRepository addressRepository; |  | ||||||
|     private final MessageRepository messageRepository; |  | ||||||
|     private final ProofOfWorkRepository proofOfWorkRepository; |  | ||||||
|     private final ProofOfWorkEngine proofOfWorkEngine; |  | ||||||
|     private final CustomCommandHandler customCommandHandler; |  | ||||||
|     private final ProofOfWorkService proofOfWorkService; |  | ||||||
|     private final Labeler labeler; |  | ||||||
|     private final NetworkHandler.MessageListener networkListener; |  | ||||||
|  |  | ||||||
|     private final TreeSet<Long> streams = new TreeSet<>(); |  | ||||||
|     private final int port; |  | ||||||
|     private final long clientNonce; |  | ||||||
|     private long connectionTTL; |  | ||||||
|     private int connectionLimit; |  | ||||||
|  |  | ||||||
|     public InternalContext(BitmessageContext.Builder builder) { |  | ||||||
|         this.cryptography = builder.cryptography; |  | ||||||
|         this.inventory = builder.inventory; |  | ||||||
|         this.nodeRegistry = builder.nodeRegistry; |  | ||||||
|         this.networkHandler = builder.networkHandler; |  | ||||||
|         this.addressRepository = builder.addressRepo; |  | ||||||
|         this.messageRepository = builder.messageRepo; |  | ||||||
|         this.proofOfWorkRepository = builder.proofOfWorkRepository; |  | ||||||
|         this.proofOfWorkService = new ProofOfWorkService(); |  | ||||||
|         this.proofOfWorkEngine = builder.proofOfWorkEngine; |  | ||||||
|         this.clientNonce = cryptography.randomNonce(); |  | ||||||
|         this.customCommandHandler = builder.customCommandHandler; |  | ||||||
|         this.port = builder.port; |  | ||||||
|         this.connectionLimit = builder.connectionLimit; |  | ||||||
|         this.connectionTTL = builder.connectionTTL; |  | ||||||
|         this.labeler = builder.labeler; |  | ||||||
|         this.networkListener = new DefaultMessageListener(labeler, builder.listener); |  | ||||||
|  |  | ||||||
|         Singleton.initialize(cryptography); |  | ||||||
|  |  | ||||||
|         // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. |  | ||||||
|         for (BitmessageAddress address : addressRepository.getIdentities()) { |  | ||||||
|             streams.add(address.getStream()); |  | ||||||
|         } |  | ||||||
|         for (BitmessageAddress address : addressRepository.getSubscriptions()) { |  | ||||||
|             streams.add(address.getStream()); |  | ||||||
|         } |  | ||||||
|         if (streams.isEmpty()) { |  | ||||||
|             streams.add(1L); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, |  | ||||||
|             proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, builder.labeler, |  | ||||||
|             networkListener); |  | ||||||
|         for (BitmessageAddress identity : addressRepository.getIdentities()) { |  | ||||||
|             streams.add(identity.getStream()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void init(Object... objects) { |  | ||||||
|         for (Object o : objects) { |  | ||||||
|             if (o instanceof ContextHolder) { |  | ||||||
|                 ((ContextHolder) o).setContext(this); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Cryptography getCryptography() { |  | ||||||
|         return cryptography; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Inventory getInventory() { |  | ||||||
|         return inventory; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public NodeRegistry getNodeRegistry() { |  | ||||||
|         return nodeRegistry; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public NetworkHandler getNetworkHandler() { |  | ||||||
|         return networkHandler; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public AddressRepository getAddressRepository() { |  | ||||||
|         return addressRepository; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public MessageRepository getMessageRepository() { |  | ||||||
|         return messageRepository; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ProofOfWorkRepository getProofOfWorkRepository() { |  | ||||||
|         return proofOfWorkRepository; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ProofOfWorkEngine getProofOfWorkEngine() { |  | ||||||
|         return proofOfWorkEngine; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ProofOfWorkService getProofOfWorkService() { |  | ||||||
|         return proofOfWorkService; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Labeler getLabeler() { |  | ||||||
|         return labeler; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public NetworkHandler.MessageListener getNetworkListener() { |  | ||||||
|         return networkListener; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long[] getStreams() { |  | ||||||
|         long[] result = new long[streams.size()]; |  | ||||||
|         int i = 0; |  | ||||||
|         for (long stream : streams) { |  | ||||||
|             result[i++] = stream; |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int getPort() { |  | ||||||
|         return port; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void send(final Plaintext plaintext) { |  | ||||||
|         if (plaintext.getAckMessage() != null) { |  | ||||||
|             long expires = UnixTime.now(+plaintext.getTTL()); |  | ||||||
|             LOG.info("Expires at " + expires); |  | ||||||
|             proofOfWorkService.doProofOfWorkWithAck(plaintext, expires); |  | ||||||
|         } else { |  | ||||||
|             send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), plaintext.getTTL()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload, |  | ||||||
|                      final long timeToLive) { |  | ||||||
|         try { |  | ||||||
|             final BitmessageAddress recipient = (to != null ? to : from); |  | ||||||
|             long expires = UnixTime.now(+timeToLive); |  | ||||||
|             LOG.info("Expires at " + expires); |  | ||||||
|             final ObjectMessage object = new ObjectMessage.Builder() |  | ||||||
|                 .stream(recipient.getStream()) |  | ||||||
|                 .expiresTime(expires) |  | ||||||
|                 .payload(payload) |  | ||||||
|                 .build(); |  | ||||||
|             if (object.isSigned()) { |  | ||||||
|                 object.sign(from.getPrivateKey()); |  | ||||||
|             } |  | ||||||
|             if (payload instanceof Broadcast) { |  | ||||||
|                 ((Broadcast) payload).encrypt(); |  | ||||||
|             } else if (payload instanceof Encrypted) { |  | ||||||
|                 object.encrypt(recipient.getPubkey()); |  | ||||||
|             } |  | ||||||
|             proofOfWorkService.doProofOfWork(to, object); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void sendPubkey(final BitmessageAddress identity, final long targetStream) { |  | ||||||
|         try { |  | ||||||
|             long expires = UnixTime.now(TTL.pubkey()); |  | ||||||
|             LOG.info("Expires at " + expires); |  | ||||||
|             final ObjectMessage response = new ObjectMessage.Builder() |  | ||||||
|                 .stream(targetStream) |  | ||||||
|                 .expiresTime(expires) |  | ||||||
|                 .payload(identity.getPubkey()) |  | ||||||
|                 .build(); |  | ||||||
|             response.sign(identity.getPrivateKey()); |  | ||||||
|             response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey())); |  | ||||||
|             // TODO: remember that the pubkey is just about to be sent, and on which stream! |  | ||||||
|             proofOfWorkService.doProofOfWork(response); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback |  | ||||||
|      * for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB. |  | ||||||
|      */ |  | ||||||
|     public void requestPubkey(final BitmessageAddress contact) { |  | ||||||
|         threadPool.execute(new Runnable() { |  | ||||||
|             @Override |  | ||||||
|             public void run() { |  | ||||||
|                 BitmessageAddress stored = addressRepository.getAddress(contact.getAddress()); |  | ||||||
|  |  | ||||||
|                 tryToFindMatchingPubkey(contact); |  | ||||||
|                 if (contact.getPubkey() != null) { |  | ||||||
|                     if (stored != null) { |  | ||||||
|                         stored.setPubkey(contact.getPubkey()); |  | ||||||
|                         addressRepository.save(stored); |  | ||||||
|                     } else { |  | ||||||
|                         addressRepository.save(contact); |  | ||||||
|                     } |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (stored == null) { |  | ||||||
|                     addressRepository.save(contact); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 long expires = UnixTime.now(TTL.getpubkey()); |  | ||||||
|                 LOG.info("Expires at " + expires); |  | ||||||
|                 final ObjectMessage request = new ObjectMessage.Builder() |  | ||||||
|                     .stream(contact.getStream()) |  | ||||||
|                     .expiresTime(expires) |  | ||||||
|                     .payload(new GetPubkey(contact)) |  | ||||||
|                     .build(); |  | ||||||
|                 proofOfWorkService.doProofOfWork(request); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void tryToFindMatchingPubkey(BitmessageAddress address) { |  | ||||||
|         BitmessageAddress stored = addressRepository.getAddress(address.getAddress()); |  | ||||||
|         if (stored != null) { |  | ||||||
|             address.setAlias(stored.getAlias()); |  | ||||||
|             address.setSubscribed(stored.isSubscribed()); |  | ||||||
|         } |  | ||||||
|         for (ObjectMessage object : inventory.getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) { |  | ||||||
|             try { |  | ||||||
|                 Pubkey pubkey = (Pubkey) object.getPayload(); |  | ||||||
|                 if (address.getVersion() == 4) { |  | ||||||
|                     V4Pubkey v4Pubkey = (V4Pubkey) pubkey; |  | ||||||
|                     if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) { |  | ||||||
|                         v4Pubkey.decrypt(address.getPublicDecryptionKey()); |  | ||||||
|                         if (object.isSignatureValid(v4Pubkey)) { |  | ||||||
|                             address.setPubkey(v4Pubkey); |  | ||||||
|                             addressRepository.save(address); |  | ||||||
|                             break; |  | ||||||
|                         } else { |  | ||||||
|                             LOG.info("Found pubkey for " + address + " but signature is invalid"); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if (Arrays.equals(pubkey.getRipe(), address.getRipe())) { |  | ||||||
|                         address.setPubkey(pubkey); |  | ||||||
|                         addressRepository.save(address); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } catch (Exception e) { |  | ||||||
|                 LOG.debug(e.getMessage(), e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void resendUnacknowledged() { |  | ||||||
|         List<Plaintext> messages = messageRepository.findMessagesToResend(); |  | ||||||
|         for (Plaintext message : messages) { |  | ||||||
|             send(message); |  | ||||||
|             messageRepository.save(message); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getClientNonce() { |  | ||||||
|         return clientNonce; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getConnectionTTL() { |  | ||||||
|         return connectionTTL; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int getConnectionLimit() { |  | ||||||
|         return connectionLimit; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public CustomCommandHandler getCustomCommandHandler() { |  | ||||||
|         return customCommandHandler; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public interface ContextHolder { |  | ||||||
|         void setContext(InternalContext context); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,125 +0,0 @@ | |||||||
| package ch.dissem.bitmessage; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.*; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Msg; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; |  | ||||||
| import ch.dissem.bitmessage.ports.Cryptography; |  | ||||||
| import ch.dissem.bitmessage.ports.MessageRepository; |  | ||||||
| import ch.dissem.bitmessage.ports.ProofOfWorkEngine; |  | ||||||
| import ch.dissem.bitmessage.ports.ProofOfWorkRepository; |  | ||||||
| import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Timer; |  | ||||||
| import java.util.TimerTask; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; |  | ||||||
| import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @author Christian Basler |  | ||||||
|  */ |  | ||||||
| public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder { |  | ||||||
|     private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class); |  | ||||||
|  |  | ||||||
|     private Cryptography cryptography; |  | ||||||
|     private InternalContext ctx; |  | ||||||
|     private ProofOfWorkRepository powRepo; |  | ||||||
|     private MessageRepository messageRepo; |  | ||||||
|  |  | ||||||
|     public void doMissingProofOfWork(long delayInMilliseconds) { |  | ||||||
|         final List<byte[]> items = powRepo.getItems(); |  | ||||||
|         if (items.isEmpty()) return; |  | ||||||
|  |  | ||||||
|         // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU |  | ||||||
|         new Timer().schedule(new TimerTask() { |  | ||||||
|             @Override |  | ||||||
|             public void run() { |  | ||||||
|                 LOG.info("Doing POW for " + items.size() + " tasks."); |  | ||||||
|                 for (byte[] initialHash : items) { |  | ||||||
|                     Item item = powRepo.getItem(initialHash); |  | ||||||
|                     cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, |  | ||||||
|                         ProofOfWorkService.this); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }, delayInMilliseconds); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void doProofOfWork(ObjectMessage object) { |  | ||||||
|         doProofOfWork(null, object); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) { |  | ||||||
|         Pubkey pubkey = recipient == null ? null : recipient.getPubkey(); |  | ||||||
|  |  | ||||||
|         long nonceTrialsPerByte = pubkey == null ? NETWORK_NONCE_TRIALS_PER_BYTE : pubkey.getNonceTrialsPerByte(); |  | ||||||
|         long extraBytes = pubkey == null ? NETWORK_EXTRA_BYTES : pubkey.getExtraBytes(); |  | ||||||
|  |  | ||||||
|         powRepo.putObject(object, nonceTrialsPerByte, extraBytes); |  | ||||||
|         if (object.getPayload() instanceof PlaintextHolder) { |  | ||||||
|             Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext(); |  | ||||||
|             plaintext.setInitialHash(cryptography.getInitialHash(object)); |  | ||||||
|             messageRepo.save(plaintext); |  | ||||||
|         } |  | ||||||
|         cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) { |  | ||||||
|         final ObjectMessage ack = plaintext.getAckMessage(); |  | ||||||
|         messageRepo.save(plaintext); |  | ||||||
|         Item item = new Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, |  | ||||||
|             expirationTime, plaintext); |  | ||||||
|         powRepo.putObject(item); |  | ||||||
|         cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onNonceCalculated(byte[] initialHash, byte[] nonce) { |  | ||||||
|         Item item = powRepo.getItem(initialHash); |  | ||||||
|         if (item.message == null) { |  | ||||||
|             ObjectMessage object = item.object; |  | ||||||
|             object.setNonce(nonce); |  | ||||||
|             Plaintext plaintext = messageRepo.getMessage(initialHash); |  | ||||||
|             if (plaintext != null) { |  | ||||||
|                 plaintext.setInventoryVector(object.getInventoryVector()); |  | ||||||
|                 plaintext.updateNextTry(); |  | ||||||
|                 ctx.getLabeler().markAsSent(plaintext); |  | ||||||
|                 messageRepo.save(plaintext); |  | ||||||
|             } |  | ||||||
|             try { |  | ||||||
|                 ctx.getNetworkListener().receive(object); |  | ||||||
|             } catch (IOException e) { |  | ||||||
|                 LOG.debug(e.getMessage(), e); |  | ||||||
|             } |  | ||||||
|             ctx.getInventory().storeObject(object); |  | ||||||
|             ctx.getNetworkHandler().offer(object.getInventoryVector()); |  | ||||||
|         } else { |  | ||||||
|             item.message.getAckMessage().setNonce(nonce); |  | ||||||
|             final ObjectMessage object = new ObjectMessage.Builder() |  | ||||||
|                 .stream(item.message.getStream()) |  | ||||||
|                 .expiresTime(item.expirationTime) |  | ||||||
|                 .payload(new Msg(item.message)) |  | ||||||
|                 .build(); |  | ||||||
|             if (object.isSigned()) { |  | ||||||
|                 object.sign(item.message.getFrom().getPrivateKey()); |  | ||||||
|             } |  | ||||||
|             if (object.getPayload() instanceof Encrypted) { |  | ||||||
|                 object.encrypt(item.message.getTo().getPubkey()); |  | ||||||
|             } |  | ||||||
|             doProofOfWork(item.message.getTo(), object); |  | ||||||
|         } |  | ||||||
|         powRepo.removeObject(initialHash); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setContext(InternalContext ctx) { |  | ||||||
|         this.ctx = ctx; |  | ||||||
|         this.cryptography = cryptography(); |  | ||||||
|         this.powRepo = ctx.getProofOfWorkRepository(); |  | ||||||
|         this.messageRepo = ctx.getMessageRepository(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,83 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The 'addr' command holds a list of known active Bitmessage nodes. |  | ||||||
|  */ |  | ||||||
| public class Addr implements MessagePayload { |  | ||||||
|     private static final long serialVersionUID = -5117688017050138720L; |  | ||||||
|  |  | ||||||
|     private final List<NetworkAddress> addresses; |  | ||||||
|  |  | ||||||
|     private Addr(Builder builder) { |  | ||||||
|         addresses = builder.addresses; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Command getCommand() { |  | ||||||
|         return Command.ADDR; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<NetworkAddress> getAddresses() { |  | ||||||
|         return addresses; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         Encode.varInt(addresses.size(), out); |  | ||||||
|         for (NetworkAddress address : addresses) { |  | ||||||
|             address.write(out); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         Encode.varInt(addresses.size(), buffer); |  | ||||||
|         for (NetworkAddress address : addresses) { |  | ||||||
|             address.write(buffer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private List<NetworkAddress> addresses = new ArrayList<>(); |  | ||||||
|  |  | ||||||
|         public Builder addresses(Collection<NetworkAddress> addresses){ |  | ||||||
|             this.addresses.addAll(addresses); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addAddress(final NetworkAddress address) { |  | ||||||
|             this.addresses.add(address); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Addr build() { |  | ||||||
|             return new Addr(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,277 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.V4Pubkey; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.PrivateKey; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.AccessCounter; |  | ||||||
| import ch.dissem.bitmessage.utils.Base58; |  | ||||||
| import ch.dissem.bitmessage.utils.Bytes; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayInputStream; |  | ||||||
| import java.io.ByteArrayOutputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Decode.bytes; |  | ||||||
| import static ch.dissem.bitmessage.utils.Decode.varInt; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address |  | ||||||
|  * holding private keys. |  | ||||||
|  */ |  | ||||||
| public class BitmessageAddress implements Serializable { |  | ||||||
|     private static final long serialVersionUID = 2386328540805994064L; |  | ||||||
|  |  | ||||||
|     private final long version; |  | ||||||
|     private final long stream; |  | ||||||
|     private final byte[] ripe; |  | ||||||
|     private final byte[] tag; |  | ||||||
|     /** |  | ||||||
|      * Used for V4 address encryption. It's easier to just create it regardless of address version. |  | ||||||
|      */ |  | ||||||
|     private final byte[] publicDecryptionKey; |  | ||||||
|  |  | ||||||
|     private String address; |  | ||||||
|  |  | ||||||
|     private PrivateKey privateKey; |  | ||||||
|     private Pubkey pubkey; |  | ||||||
|  |  | ||||||
|     private String alias; |  | ||||||
|     private boolean subscribed; |  | ||||||
|     private boolean chan; |  | ||||||
|  |  | ||||||
|     BitmessageAddress(long version, long stream, byte[] ripe) { |  | ||||||
|         try { |  | ||||||
|             this.version = version; |  | ||||||
|             this.stream = stream; |  | ||||||
|             this.ripe = ripe; |  | ||||||
|  |  | ||||||
|             ByteArrayOutputStream os = new ByteArrayOutputStream(); |  | ||||||
|             Encode.varInt(version, os); |  | ||||||
|             Encode.varInt(stream, os); |  | ||||||
|             if (version < 4) { |  | ||||||
|                 byte[] checksum = cryptography().sha512(os.toByteArray(), ripe); |  | ||||||
|                 this.tag = null; |  | ||||||
|                 this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); |  | ||||||
|             } else { |  | ||||||
|                 // for tag and decryption key, the checksum has to be created with 0x00 padding |  | ||||||
|                 byte[] checksum = cryptography().doubleSha512(os.toByteArray(), ripe); |  | ||||||
|                 this.tag = Arrays.copyOfRange(checksum, 32, 64); |  | ||||||
|                 this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); |  | ||||||
|             } |  | ||||||
|             // but for the address and its checksum they need to be stripped |  | ||||||
|             int offset = Bytes.numberOfLeadingZeros(ripe); |  | ||||||
|             os.write(ripe, offset, ripe.length - offset); |  | ||||||
|             byte[] checksum = cryptography().doubleSha512(os.toByteArray()); |  | ||||||
|             os.write(checksum, 0, 4); |  | ||||||
|             this.address = "BM-" + Base58.encode(os.toByteArray()); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress(Pubkey publicKey) { |  | ||||||
|         this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe()); |  | ||||||
|         this.pubkey = publicKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress(String address, String passphrase) { |  | ||||||
|         this(address); |  | ||||||
|         this.privateKey = new PrivateKey(this, passphrase); |  | ||||||
|         this.pubkey = this.privateKey.getPubkey(); |  | ||||||
|         if (!Arrays.equals(ripe, privateKey.getPubkey().getRipe())) { |  | ||||||
|             throw new IllegalArgumentException("Wrong address or passphrase"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static BitmessageAddress chan(String address, String passphrase) { |  | ||||||
|         BitmessageAddress result = new BitmessageAddress(address, passphrase); |  | ||||||
|         result.chan = true; |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static BitmessageAddress chan(long stream, String passphrase) { |  | ||||||
|         PrivateKey privateKey = new PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase); |  | ||||||
|         BitmessageAddress result = new BitmessageAddress(privateKey); |  | ||||||
|         result.chan = true; |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static List<BitmessageAddress> deterministic(String passphrase, int numberOfAddresses, |  | ||||||
|                                                         long version, long stream, boolean shorter) { |  | ||||||
|         List<BitmessageAddress> result = new ArrayList<>(numberOfAddresses); |  | ||||||
|         List<PrivateKey> privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter); |  | ||||||
|         for (PrivateKey pk : privateKeys) { |  | ||||||
|             result.add(new BitmessageAddress(pk)); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress(PrivateKey privateKey) { |  | ||||||
|         this(privateKey.getPubkey()); |  | ||||||
|         this.privateKey = privateKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress(String address) { |  | ||||||
|         try { |  | ||||||
|             this.address = address; |  | ||||||
|             byte[] bytes = Base58.decode(address.substring(3)); |  | ||||||
|             ByteArrayInputStream in = new ByteArrayInputStream(bytes); |  | ||||||
|             AccessCounter counter = new AccessCounter(); |  | ||||||
|             this.version = varInt(in, counter); |  | ||||||
|             this.stream = varInt(in, counter); |  | ||||||
|             this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20); |  | ||||||
|  |  | ||||||
|             // test checksum |  | ||||||
|             byte[] checksum = cryptography().doubleSha512(bytes, bytes.length - 4); |  | ||||||
|             byte[] expectedChecksum = bytes(in, 4); |  | ||||||
|             for (int i = 0; i < 4; i++) { |  | ||||||
|                 if (expectedChecksum[i] != checksum[i]) |  | ||||||
|                     throw new IllegalArgumentException("Checksum of address failed"); |  | ||||||
|             } |  | ||||||
|             if (version < 4) { |  | ||||||
|                 checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); |  | ||||||
|                 this.tag = null; |  | ||||||
|                 this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); |  | ||||||
|             } else { |  | ||||||
|                 checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); |  | ||||||
|                 this.tag = Arrays.copyOfRange(checksum, 32, 64); |  | ||||||
|                 this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); |  | ||||||
|             } |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] calculateTag(long version, long stream, byte[] ripe) { |  | ||||||
|         try { |  | ||||||
|             ByteArrayOutputStream out = new ByteArrayOutputStream(); |  | ||||||
|             Encode.varInt(version, out); |  | ||||||
|             Encode.varInt(stream, out); |  | ||||||
|             out.write(ripe); |  | ||||||
|             return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getVersion() { |  | ||||||
|         return version; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Pubkey getPubkey() { |  | ||||||
|         return pubkey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setPubkey(Pubkey pubkey) { |  | ||||||
|         if (pubkey instanceof V4Pubkey) { |  | ||||||
|             if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag())) |  | ||||||
|                 throw new IllegalArgumentException("Pubkey has incompatible tag"); |  | ||||||
|         } |  | ||||||
|         if (!Arrays.equals(ripe, pubkey.getRipe())) |  | ||||||
|             throw new IllegalArgumentException("Pubkey has incompatible ripe"); |  | ||||||
|         this.pubkey = pubkey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. |  | ||||||
|      */ |  | ||||||
|     public byte[] getPublicDecryptionKey() { |  | ||||||
|         return publicDecryptionKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public PrivateKey getPrivateKey() { |  | ||||||
|         return privateKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getAddress() { |  | ||||||
|         return address; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getAlias() { |  | ||||||
|         return alias; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setAlias(String alias) { |  | ||||||
|         this.alias = alias; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         return alias == null ? address : alias; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getRipe() { |  | ||||||
|         return ripe; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getTag() { |  | ||||||
|         return tag; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         BitmessageAddress address = (BitmessageAddress) o; |  | ||||||
|         return Objects.equals(version, address.version) && |  | ||||||
|                 Objects.equals(stream, address.stream) && |  | ||||||
|                 Arrays.equals(ripe, address.ripe); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Arrays.hashCode(ripe); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isSubscribed() { |  | ||||||
|         return subscribed; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setSubscribed(boolean subscribed) { |  | ||||||
|         this.subscribed = subscribed; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isChan() { |  | ||||||
|         return chan; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setChan(boolean chan) { |  | ||||||
|         this.chan = chan; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean has(Feature feature) { |  | ||||||
|         if (pubkey == null || feature == null) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         return feature.isActive(pubkey.getBehaviorBitfield()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,111 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.AccessCounter; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.*; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Decode.bytes; |  | ||||||
| import static ch.dissem.bitmessage.utils.Decode.varString; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @author Christian Basler |  | ||||||
|  */ |  | ||||||
| public class CustomMessage implements MessagePayload { |  | ||||||
|     private static final long serialVersionUID = -8932056829480326011L; |  | ||||||
|  |  | ||||||
|     public static final String COMMAND_ERROR = "ERROR"; |  | ||||||
|  |  | ||||||
|     private final String command; |  | ||||||
|     private final byte[] data; |  | ||||||
|  |  | ||||||
|     public CustomMessage(String command) { |  | ||||||
|         this.command = command; |  | ||||||
|         this.data = null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public CustomMessage(String command, byte[] data) { |  | ||||||
|         this.command = command; |  | ||||||
|         this.data = data; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static CustomMessage read(InputStream in, int length) throws IOException { |  | ||||||
|         AccessCounter counter = new AccessCounter(); |  | ||||||
|         return new CustomMessage(varString(in, counter), bytes(in, length - counter.length())); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Command getCommand() { |  | ||||||
|         return Command.CUSTOM; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getCustomCommand() { |  | ||||||
|         return command; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getData() { |  | ||||||
|         if (data != null) { |  | ||||||
|             return data; |  | ||||||
|         } else { |  | ||||||
|             try { |  | ||||||
|                 ByteArrayOutputStream out = new ByteArrayOutputStream(); |  | ||||||
|                 write(out); |  | ||||||
|                 return out.toByteArray(); |  | ||||||
|             } catch (IOException e) { |  | ||||||
|                 throw new ApplicationException(e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         if (data != null) { |  | ||||||
|             Encode.varString(command, out); |  | ||||||
|             out.write(data); |  | ||||||
|         } else { |  | ||||||
|             throw new ApplicationException("Tried to write custom message without data. " + |  | ||||||
|                     "Programmer: did you forget to override #write()?"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         if (data != null) { |  | ||||||
|             Encode.varString(command, buffer); |  | ||||||
|             buffer.put(data); |  | ||||||
|         } else { |  | ||||||
|             throw new ApplicationException("Tried to write custom message without data. " + |  | ||||||
|                     "Programmer: did you forget to override #write()?"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isError() { |  | ||||||
|         return COMMAND_ERROR.equals(command); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static CustomMessage error(String message) { |  | ||||||
|         try { |  | ||||||
|             return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8")); |  | ||||||
|         } catch (UnsupportedEncodingException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,84 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.LinkedList; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The 'getdata' command is used to request objects from a node. |  | ||||||
|  */ |  | ||||||
| public class GetData implements MessagePayload { |  | ||||||
|     private static final long serialVersionUID = 1433878785969631061L; |  | ||||||
|  |  | ||||||
|     public static final int MAX_INVENTORY_SIZE = 50_000; |  | ||||||
|  |  | ||||||
|     List<InventoryVector> inventory; |  | ||||||
|  |  | ||||||
|     private GetData(Builder builder) { |  | ||||||
|         inventory = builder.inventory; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Command getCommand() { |  | ||||||
|         return Command.GETDATA; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<InventoryVector> getInventory() { |  | ||||||
|         return inventory; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         Encode.varInt(inventory.size(), out); |  | ||||||
|         for (InventoryVector iv : inventory) { |  | ||||||
|             iv.write(out); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         Encode.varInt(inventory.size(), buffer); |  | ||||||
|         for (InventoryVector iv : inventory) { |  | ||||||
|             iv.write(buffer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private List<InventoryVector> inventory = new LinkedList<>(); |  | ||||||
|  |  | ||||||
|         public Builder addInventoryVector(InventoryVector inventoryVector) { |  | ||||||
|             this.inventory.add(inventoryVector); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder inventory(List<InventoryVector> inventory) { |  | ||||||
|             this.inventory = inventory; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public GetData build() { |  | ||||||
|             return new GetData(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.LinkedList; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items. |  | ||||||
|  */ |  | ||||||
| public class Inv implements MessagePayload { |  | ||||||
|     private static final long serialVersionUID = 3662992522956947145L; |  | ||||||
|  |  | ||||||
|     private List<InventoryVector> inventory; |  | ||||||
|  |  | ||||||
|     private Inv(Builder builder) { |  | ||||||
|         inventory = builder.inventory; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<InventoryVector> getInventory() { |  | ||||||
|         return inventory; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Command getCommand() { |  | ||||||
|         return Command.INV; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         Encode.varInt(inventory.size(), out); |  | ||||||
|         for (InventoryVector iv : inventory) { |  | ||||||
|             iv.write(out); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         Encode.varInt(inventory.size(), buffer); |  | ||||||
|         for (InventoryVector iv : inventory) { |  | ||||||
|             iv.write(buffer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private List<InventoryVector> inventory = new LinkedList<>(); |  | ||||||
|  |  | ||||||
|         public Builder addInventoryVector(InventoryVector inventoryVector) { |  | ||||||
|             this.inventory.add(inventoryVector); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder inventory(List<InventoryVector> inventory) { |  | ||||||
|             this.inventory = inventory; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Inv build() { |  | ||||||
|             return new Inv(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,151 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.io.UnsupportedEncodingException; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.security.GeneralSecurityException; |  | ||||||
| import java.security.NoSuchAlgorithmException; |  | ||||||
| import java.security.NoSuchProviderException; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A network message is exchanged between two nodes. |  | ||||||
|  */ |  | ||||||
| public class NetworkMessage implements Streamable { |  | ||||||
|     private static final long serialVersionUID = 702708857104464809L; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Magic value indicating message origin network, and used to seek to next message when stream state is unknown |  | ||||||
|      */ |  | ||||||
|     public final static int MAGIC = 0xE9BEB4D9; |  | ||||||
|     public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array(); |  | ||||||
|  |  | ||||||
|     private final MessagePayload payload; |  | ||||||
|  |  | ||||||
|     public NetworkMessage(MessagePayload payload) { |  | ||||||
|         this.payload = payload; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * First 4 bytes of sha512(payload) |  | ||||||
|      */ |  | ||||||
|     private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException { |  | ||||||
|         byte[] d = cryptography().sha512(bytes); |  | ||||||
|         return new byte[]{d[0], d[1], d[2], d[3]}; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The actual data, a message or an object. Not to be confused with objectPayload. |  | ||||||
|      */ |  | ||||||
|     public MessagePayload getPayload() { |  | ||||||
|         return payload; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         // magic |  | ||||||
|         Encode.int32(MAGIC, out); |  | ||||||
|  |  | ||||||
|         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) |  | ||||||
|         String command = payload.getCommand().name().toLowerCase(); |  | ||||||
|         out.write(command.getBytes("ASCII")); |  | ||||||
|         for (int i = command.length(); i < 12; i++) { |  | ||||||
|             out.write('\0'); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         byte[] payloadBytes = Encode.bytes(payload); |  | ||||||
|  |  | ||||||
|         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would |  | ||||||
|         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are |  | ||||||
|         // larger than this. |  | ||||||
|         Encode.int32(payloadBytes.length, out); |  | ||||||
|  |  | ||||||
|         // checksum |  | ||||||
|         try { |  | ||||||
|             out.write(getChecksum(payloadBytes)); |  | ||||||
|         } catch (GeneralSecurityException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // message payload |  | ||||||
|         out.write(payloadBytes); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * A more efficient implementation of the write method, writing header data to the provided buffer and returning |  | ||||||
|      * a new buffer containing the payload. |  | ||||||
|      * |  | ||||||
|      * @param headerBuffer where the header data is written to (24 bytes) |  | ||||||
|      * @return a buffer containing the payload, ready to be read. |  | ||||||
|      */ |  | ||||||
|     public ByteBuffer writeHeaderAndGetPayloadBuffer(ByteBuffer headerBuffer) { |  | ||||||
|         return ByteBuffer.wrap(writeHeader(headerBuffer)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * For improved memory efficiency, you should use {@link #writeHeaderAndGetPayloadBuffer(ByteBuffer)} |  | ||||||
|      * and write the header buffer as well as the returned payload buffer into the channel. |  | ||||||
|      * |  | ||||||
|      * @param buffer where everything gets written to. Needs to be large enough for the whole message |  | ||||||
|      *               to be written. |  | ||||||
|      */ |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         byte[] payloadBytes = writeHeader(buffer); |  | ||||||
|         buffer.put(payloadBytes); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private byte[] writeHeader(ByteBuffer out) { |  | ||||||
|         // magic |  | ||||||
|         Encode.int32(MAGIC, out); |  | ||||||
|  |  | ||||||
|         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) |  | ||||||
|         String command = payload.getCommand().name().toLowerCase(); |  | ||||||
|         try { |  | ||||||
|             out.put(command.getBytes("ASCII")); |  | ||||||
|         } catch (UnsupportedEncodingException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|         for (int i = command.length(); i < 12; i++) { |  | ||||||
|             out.put((byte) 0); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         byte[] payloadBytes = Encode.bytes(payload); |  | ||||||
|  |  | ||||||
|         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would |  | ||||||
|         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are |  | ||||||
|         // larger than this. |  | ||||||
|         Encode.int32(payloadBytes.length, out); |  | ||||||
|  |  | ||||||
|         // checksum |  | ||||||
|         try { |  | ||||||
|             out.put(getChecksum(payloadBytes)); |  | ||||||
|         } catch (GeneralSecurityException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // message payload |  | ||||||
|         return payloadBytes; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,271 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.payload.ObjectPayload; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.ObjectType; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.PrivateKey; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; |  | ||||||
| import ch.dissem.bitmessage.utils.Bytes; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayOutputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The 'object' command sends an object that is shared throughout the network. |  | ||||||
|  */ |  | ||||||
| public class ObjectMessage implements MessagePayload { |  | ||||||
|     private static final long serialVersionUID = 2495752480120659139L; |  | ||||||
|  |  | ||||||
|     private byte[] nonce; |  | ||||||
|     private long expiresTime; |  | ||||||
|     private long objectType; |  | ||||||
|     /** |  | ||||||
|      * The object's version |  | ||||||
|      */ |  | ||||||
|     private long version; |  | ||||||
|     private long stream; |  | ||||||
|  |  | ||||||
|     private ObjectPayload payload; |  | ||||||
|     private byte[] payloadBytes; |  | ||||||
|  |  | ||||||
|     private ObjectMessage(Builder builder) { |  | ||||||
|         nonce = builder.nonce; |  | ||||||
|         expiresTime = builder.expiresTime; |  | ||||||
|         objectType = builder.objectType; |  | ||||||
|         version = builder.payload.getVersion(); |  | ||||||
|         stream = builder.streamNumber > 0 ? builder.streamNumber : builder.payload.getStream(); |  | ||||||
|         payload = builder.payload; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Command getCommand() { |  | ||||||
|         return Command.OBJECT; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getNonce() { |  | ||||||
|         return nonce; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setNonce(byte[] nonce) { |  | ||||||
|         this.nonce = nonce; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getExpiresTime() { |  | ||||||
|         return expiresTime; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getType() { |  | ||||||
|         return objectType; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ObjectPayload getPayload() { |  | ||||||
|         return payload; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getVersion() { |  | ||||||
|         return version; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public InventoryVector getInventoryVector() { |  | ||||||
|         return InventoryVector.fromHash( |  | ||||||
|                 Bytes.truncate(cryptography().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32) |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private boolean isEncrypted() { |  | ||||||
|         return payload instanceof Encrypted && !((Encrypted) payload).isDecrypted(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isSigned() { |  | ||||||
|         return payload.isSigned(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private byte[] getBytesToSign() { |  | ||||||
|         try { |  | ||||||
|             ByteArrayOutputStream out = new ByteArrayOutputStream(); |  | ||||||
|             writeHeaderWithoutNonce(out); |  | ||||||
|             payload.writeBytesToSign(out); |  | ||||||
|             return out.toByteArray(); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void sign(PrivateKey key) { |  | ||||||
|         if (payload.isSigned()) { |  | ||||||
|             payload.setSignature(cryptography().getSignature(getBytesToSign(), key)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void decrypt(PrivateKey key) throws IOException, DecryptionFailedException { |  | ||||||
|         if (payload instanceof Encrypted) { |  | ||||||
|             ((Encrypted) payload).decrypt(key.getPrivateEncryptionKey()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void decrypt(byte[] privateEncryptionKey) throws IOException, DecryptionFailedException { |  | ||||||
|         if (payload instanceof Encrypted) { |  | ||||||
|             ((Encrypted) payload).decrypt(privateEncryptionKey); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void encrypt(byte[] publicEncryptionKey) throws IOException { |  | ||||||
|         if (payload instanceof Encrypted) { |  | ||||||
|             ((Encrypted) payload).encrypt(publicEncryptionKey); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void encrypt(Pubkey publicKey) { |  | ||||||
|         try { |  | ||||||
|             if (payload instanceof Encrypted) { |  | ||||||
|                 ((Encrypted) payload).encrypt(publicKey.getEncryptionKey()); |  | ||||||
|             } |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isSignatureValid(Pubkey pubkey) throws IOException { |  | ||||||
|         if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first"); |  | ||||||
|         return cryptography().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         if (nonce == null) { |  | ||||||
|             out.write(new byte[8]); |  | ||||||
|         } else { |  | ||||||
|             out.write(nonce); |  | ||||||
|         } |  | ||||||
|         out.write(getPayloadBytesWithoutNonce()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         if (nonce == null) { |  | ||||||
|             buffer.put(new byte[8]); |  | ||||||
|         } else { |  | ||||||
|             buffer.put(nonce); |  | ||||||
|         } |  | ||||||
|         buffer.put(getPayloadBytesWithoutNonce()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void writeHeaderWithoutNonce(OutputStream out) throws IOException { |  | ||||||
|         Encode.int64(expiresTime, out); |  | ||||||
|         Encode.int32(objectType, out); |  | ||||||
|         Encode.varInt(version, out); |  | ||||||
|         Encode.varInt(stream, out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getPayloadBytesWithoutNonce() { |  | ||||||
|         try { |  | ||||||
|             if (payloadBytes == null) { |  | ||||||
|                 ByteArrayOutputStream out = new ByteArrayOutputStream(); |  | ||||||
|                 writeHeaderWithoutNonce(out); |  | ||||||
|                 payload.write(out); |  | ||||||
|                 payloadBytes = out.toByteArray(); |  | ||||||
|             } |  | ||||||
|             return payloadBytes; |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private byte[] nonce; |  | ||||||
|         private long expiresTime; |  | ||||||
|         private long objectType = -1; |  | ||||||
|         private long streamNumber; |  | ||||||
|         private ObjectPayload payload; |  | ||||||
|  |  | ||||||
|         public Builder nonce(byte[] nonce) { |  | ||||||
|             this.nonce = nonce; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder expiresTime(long expiresTime) { |  | ||||||
|             this.expiresTime = expiresTime; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder objectType(long objectType) { |  | ||||||
|             this.objectType = objectType; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder objectType(ObjectType objectType) { |  | ||||||
|             this.objectType = objectType.getNumber(); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder stream(long streamNumber) { |  | ||||||
|             this.streamNumber = streamNumber; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder payload(ObjectPayload payload) { |  | ||||||
|             this.payload = payload; |  | ||||||
|             if (this.objectType == -1) |  | ||||||
|                 this.objectType = payload.getType().getNumber(); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public ObjectMessage build() { |  | ||||||
|             return new ObjectMessage(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|  |  | ||||||
|         ObjectMessage that = (ObjectMessage) o; |  | ||||||
|  |  | ||||||
|         return expiresTime == that.expiresTime && |  | ||||||
|                 objectType == that.objectType && |  | ||||||
|                 version == that.version && |  | ||||||
|                 stream == that.stream && |  | ||||||
|                 Objects.equals(payload, that.payload); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         int result = Arrays.hashCode(nonce); |  | ||||||
|         result = 31 * result + (int) (expiresTime ^ (expiresTime >>> 32)); |  | ||||||
|         result = 31 * result + (int) (objectType ^ (objectType >>> 32)); |  | ||||||
|         result = 31 * result + (int) (version ^ (version >>> 32)); |  | ||||||
|         result = 31 * result + (int) (stream ^ (stream >>> 32)); |  | ||||||
|         result = 31 * result + (payload != null ? payload.hashCode() : 0); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,733 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Msg; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.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 java.io.*; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.*; |  | ||||||
| import java.util.Collections; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED; |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The unencrypted message to be sent by 'msg' or 'broadcast'. |  | ||||||
|  */ |  | ||||||
| public class Plaintext implements Streamable { |  | ||||||
|     private static final long serialVersionUID = -5325729856394951079L; |  | ||||||
|  |  | ||||||
|     private final Type type; |  | ||||||
|     private final BitmessageAddress from; |  | ||||||
|     private final long encoding; |  | ||||||
|     private final byte[] message; |  | ||||||
|     private final byte[] ackData; |  | ||||||
|     private final UUID conversationId; |  | ||||||
|     private ExtendedEncoding extendedData; |  | ||||||
|     private ObjectMessage ackMessage; |  | ||||||
|     private Object id; |  | ||||||
|     private InventoryVector inventoryVector; |  | ||||||
|     private BitmessageAddress to; |  | ||||||
|     private byte[] signature; |  | ||||||
|     private Status status; |  | ||||||
|     private Long sent; |  | ||||||
|     private Long received; |  | ||||||
|  |  | ||||||
|     private Set<Label> labels; |  | ||||||
|     private byte[] initialHash; |  | ||||||
|  |  | ||||||
|     private long ttl; |  | ||||||
|     private int retries; |  | ||||||
|     private Long nextTry; |  | ||||||
|  |  | ||||||
|     private Plaintext(Builder builder) { |  | ||||||
|         id = builder.id; |  | ||||||
|         inventoryVector = builder.inventoryVector; |  | ||||||
|         type = builder.type; |  | ||||||
|         from = builder.from; |  | ||||||
|         to = builder.to; |  | ||||||
|         encoding = builder.encoding; |  | ||||||
|         message = builder.message; |  | ||||||
|         ackData = builder.ackData; |  | ||||||
|         if (builder.ackMessage != null && builder.ackMessage.length > 0) { |  | ||||||
|             ackMessage = Factory.getObjectMessage( |  | ||||||
|                 3, |  | ||||||
|                 new ByteArrayInputStream(builder.ackMessage), |  | ||||||
|                 builder.ackMessage.length); |  | ||||||
|         } |  | ||||||
|         signature = builder.signature; |  | ||||||
|         status = builder.status; |  | ||||||
|         sent = builder.sent; |  | ||||||
|         received = builder.received; |  | ||||||
|         labels = builder.labels; |  | ||||||
|         ttl = builder.ttl; |  | ||||||
|         retries = builder.retries; |  | ||||||
|         nextTry = builder.nextTry; |  | ||||||
|         conversationId = builder.conversation; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static Plaintext read(Type type, InputStream in) throws IOException { |  | ||||||
|         return readWithoutSignature(type, in) |  | ||||||
|             .signature(Decode.varBytes(in)) |  | ||||||
|             .received(UnixTime.now()) |  | ||||||
|             .build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException { |  | ||||||
|         long version = Decode.varInt(in); |  | ||||||
|         return new Builder(type) |  | ||||||
|             .addressVersion(version) |  | ||||||
|             .stream(Decode.varInt(in)) |  | ||||||
|             .behaviorBitfield(Decode.int32(in)) |  | ||||||
|             .publicSigningKey(Decode.bytes(in, 64)) |  | ||||||
|             .publicEncryptionKey(Decode.bytes(in, 64)) |  | ||||||
|             .nonceTrialsPerByte(version >= 3 ? Decode.varInt(in) : 0) |  | ||||||
|             .extraBytes(version >= 3 ? Decode.varInt(in) : 0) |  | ||||||
|             .destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null) |  | ||||||
|             .encoding(Decode.varInt(in)) |  | ||||||
|             .message(Decode.varBytes(in)) |  | ||||||
|             .ackMessage(type == Type.MSG ? Decode.varBytes(in) : null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public InventoryVector getInventoryVector() { |  | ||||||
|         return inventoryVector; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setInventoryVector(InventoryVector inventoryVector) { |  | ||||||
|         this.inventoryVector = inventoryVector; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Type getType() { |  | ||||||
|         return type; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getMessage() { |  | ||||||
|         return message; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress getFrom() { |  | ||||||
|         return from; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BitmessageAddress getTo() { |  | ||||||
|         return to; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setTo(BitmessageAddress to) { |  | ||||||
|         if (this.to.getVersion() != 0) |  | ||||||
|             throw new IllegalStateException("Correct address already set"); |  | ||||||
|         if (!Arrays.equals(this.to.getRipe(), to.getRipe())) { |  | ||||||
|             throw new IllegalArgumentException("RIPEs don't match"); |  | ||||||
|         } |  | ||||||
|         this.to = to; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Set<Label> getLabels() { |  | ||||||
|         return labels; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Encoding getEncoding() { |  | ||||||
|         return Encoding.fromCode(encoding); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getStream() { |  | ||||||
|         return from.getStream(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getSignature() { |  | ||||||
|         return signature; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setSignature(byte[] signature) { |  | ||||||
|         this.signature = signature; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isUnread() { |  | ||||||
|         for (Label label : labels) { |  | ||||||
|             if (label.getType() == Label.Type.UNREAD) { |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void write(OutputStream out, boolean includeSignature) throws IOException { |  | ||||||
|         Encode.varInt(from.getVersion(), out); |  | ||||||
|         Encode.varInt(from.getStream(), out); |  | ||||||
|         if (from.getPubkey() == null) { |  | ||||||
|             Encode.int32(0, out); |  | ||||||
|             byte[] empty = new byte[64]; |  | ||||||
|             out.write(empty); |  | ||||||
|             out.write(empty); |  | ||||||
|             if (from.getVersion() >= 3) { |  | ||||||
|                 Encode.varInt(0, out); |  | ||||||
|                 Encode.varInt(0, out); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             Encode.int32(from.getPubkey().getBehaviorBitfield(), out); |  | ||||||
|             out.write(from.getPubkey().getSigningKey(), 1, 64); |  | ||||||
|             out.write(from.getPubkey().getEncryptionKey(), 1, 64); |  | ||||||
|             if (from.getVersion() >= 3) { |  | ||||||
|                 Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out); |  | ||||||
|                 Encode.varInt(from.getPubkey().getExtraBytes(), out); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (type == Type.MSG) { |  | ||||||
|             out.write(to.getRipe()); |  | ||||||
|         } |  | ||||||
|         Encode.varInt(encoding, out); |  | ||||||
|         Encode.varInt(message.length, out); |  | ||||||
|         out.write(message); |  | ||||||
|         if (type == Type.MSG) { |  | ||||||
|             if (to.has(Feature.DOES_ACK) && getAckMessage() != null) { |  | ||||||
|                 ByteArrayOutputStream ack = new ByteArrayOutputStream(); |  | ||||||
|                 getAckMessage().write(ack); |  | ||||||
|                 Encode.varBytes(ack.toByteArray(), out); |  | ||||||
|             } else { |  | ||||||
|                 Encode.varInt(0, out); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (includeSignature) { |  | ||||||
|             if (signature == null) { |  | ||||||
|                 Encode.varInt(0, out); |  | ||||||
|             } else { |  | ||||||
|                 Encode.varInt(signature.length, out); |  | ||||||
|                 out.write(signature); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void write(ByteBuffer buffer, boolean includeSignature) { |  | ||||||
|         Encode.varInt(from.getVersion(), buffer); |  | ||||||
|         Encode.varInt(from.getStream(), buffer); |  | ||||||
|         if (from.getPubkey() == null) { |  | ||||||
|             Encode.int32(0, buffer); |  | ||||||
|             byte[] empty = new byte[64]; |  | ||||||
|             buffer.put(empty); |  | ||||||
|             buffer.put(empty); |  | ||||||
|             if (from.getVersion() >= 3) { |  | ||||||
|                 Encode.varInt(0, buffer); |  | ||||||
|                 Encode.varInt(0, buffer); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             Encode.int32(from.getPubkey().getBehaviorBitfield(), buffer); |  | ||||||
|             buffer.put(from.getPubkey().getSigningKey(), 1, 64); |  | ||||||
|             buffer.put(from.getPubkey().getEncryptionKey(), 1, 64); |  | ||||||
|             if (from.getVersion() >= 3) { |  | ||||||
|                 Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), buffer); |  | ||||||
|                 Encode.varInt(from.getPubkey().getExtraBytes(), buffer); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (type == Type.MSG) { |  | ||||||
|             buffer.put(to.getRipe()); |  | ||||||
|         } |  | ||||||
|         Encode.varInt(encoding, buffer); |  | ||||||
|         Encode.varInt(message.length, buffer); |  | ||||||
|         buffer.put(message); |  | ||||||
|         if (type == Type.MSG) { |  | ||||||
|             if (to.has(Feature.DOES_ACK) && getAckMessage() != null) { |  | ||||||
|                 Encode.varBytes(Encode.bytes(getAckMessage()), buffer); |  | ||||||
|             } else { |  | ||||||
|                 Encode.varInt(0, buffer); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (includeSignature) { |  | ||||||
|             if (signature == null) { |  | ||||||
|                 Encode.varInt(0, buffer); |  | ||||||
|             } else { |  | ||||||
|                 Encode.varInt(signature.length, buffer); |  | ||||||
|                 buffer.put(signature); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         write(out, true); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         write(buffer, true); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Object getId() { |  | ||||||
|         return id; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setId(long id) { |  | ||||||
|         if (this.id != null) throw new IllegalStateException("ID already set"); |  | ||||||
|         this.id = id; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Long getSent() { |  | ||||||
|         return sent; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Long getReceived() { |  | ||||||
|         return received; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Status getStatus() { |  | ||||||
|         return status; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setStatus(Status status) { |  | ||||||
|         if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) { |  | ||||||
|             sent = UnixTime.now(); |  | ||||||
|         } |  | ||||||
|         this.status = status; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getTTL() { |  | ||||||
|         return ttl; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int getRetries() { |  | ||||||
|         return retries; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Long getNextTry() { |  | ||||||
|         return nextTry; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void updateNextTry() { |  | ||||||
|         if (to != null) { |  | ||||||
|             if (nextTry == null) { |  | ||||||
|                 if (sent != null && to.has(Feature.DOES_ACK)) { |  | ||||||
|                     nextTry = UnixTime.now(+ttl); |  | ||||||
|                     retries++; |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 nextTry = nextTry + (1 << retries) * ttl; |  | ||||||
|                 retries++; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getSubject() { |  | ||||||
|         Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8"); |  | ||||||
|         String firstLine = s.nextLine(); |  | ||||||
|         if (encoding == EXTENDED.code) { |  | ||||||
|             if (Message.TYPE.equals(getExtendedData().getType())) { |  | ||||||
|                 return ((Message) extendedData.getContent()).getSubject(); |  | ||||||
|             } else { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|         } else if (encoding == SIMPLE.code) { |  | ||||||
|             return firstLine.substring("Subject:".length()).trim(); |  | ||||||
|         } else if (firstLine.length() > 50) { |  | ||||||
|             return firstLine.substring(0, 50).trim() + "..."; |  | ||||||
|         } else { |  | ||||||
|             return firstLine; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getText() { |  | ||||||
|         if (encoding == EXTENDED.code) { |  | ||||||
|             if (Message.TYPE.equals(getExtendedData().getType())) { |  | ||||||
|                 return ((Message) extendedData.getContent()).getBody(); |  | ||||||
|             } else { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             try { |  | ||||||
|                 String text = new String(message, "UTF-8"); |  | ||||||
|                 if (encoding == SIMPLE.code) { |  | ||||||
|                     return text.substring(text.indexOf("\nBody:") + 6); |  | ||||||
|                 } |  | ||||||
|                 return text; |  | ||||||
|             } catch (UnsupportedEncodingException e) { |  | ||||||
|                 throw new ApplicationException(e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected ExtendedEncoding getExtendedData() { |  | ||||||
|         if (extendedData == null && encoding == EXTENDED.code) { |  | ||||||
|             // TODO: make sure errors are properly handled |  | ||||||
|             extendedData = ExtendedEncodingFactory.getInstance().unzip(message); |  | ||||||
|         } |  | ||||||
|         return extendedData; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @SuppressWarnings("unchecked") |  | ||||||
|     public <T extends ExtendedEncoding.ExtendedType> T getExtendedData(Class<T> type) { |  | ||||||
|         ExtendedEncoding extendedData = getExtendedData(); |  | ||||||
|         if (extendedData == null) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         if (type == null || type.isInstance(extendedData.getContent())) { |  | ||||||
|             return (T) extendedData.getContent(); |  | ||||||
|         } |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<InventoryVector> getParents() { |  | ||||||
|         if (getExtendedData() != null && Message.TYPE.equals(getExtendedData().getType())) { |  | ||||||
|             return ((Message) extendedData.getContent()).getParents(); |  | ||||||
|         } else { |  | ||||||
|             return Collections.emptyList(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<Attachment> getFiles() { |  | ||||||
|         if (Message.TYPE.equals(getExtendedData().getType())) { |  | ||||||
|             return ((Message) extendedData.getContent()).getFiles(); |  | ||||||
|         } else { |  | ||||||
|             return Collections.emptyList(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public UUID getConversationId() { |  | ||||||
|         return conversationId; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         Plaintext plaintext = (Plaintext) o; |  | ||||||
|         return Objects.equals(encoding, plaintext.encoding) && |  | ||||||
|             Objects.equals(from, plaintext.from) && |  | ||||||
|             Arrays.equals(message, plaintext.message) && |  | ||||||
|             Objects.equals(getAckMessage(), plaintext.getAckMessage()) && |  | ||||||
|             Arrays.equals(to == null ? null : to.getRipe(), plaintext.to == null ? null : plaintext.to.getRipe()) && |  | ||||||
|             Arrays.equals(signature, plaintext.signature) && |  | ||||||
|             Objects.equals(status, plaintext.status) && |  | ||||||
|             Objects.equals(sent, plaintext.sent) && |  | ||||||
|             Objects.equals(received, plaintext.received) && |  | ||||||
|             Objects.equals(labels, plaintext.labels); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void addLabels(Label... labels) { |  | ||||||
|         if (labels != null) { |  | ||||||
|             Collections.addAll(this.labels, labels); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void addLabels(Collection<Label> labels) { |  | ||||||
|         if (labels != null) { |  | ||||||
|             this.labels.addAll(labels); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void removeLabel(Label.Type type) { |  | ||||||
|         Iterator<Label> iterator = labels.iterator(); |  | ||||||
|         while (iterator.hasNext()) { |  | ||||||
|             Label label = iterator.next(); |  | ||||||
|             if (label.getType() == type) { |  | ||||||
|                 iterator.remove(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getAckData() { |  | ||||||
|         return ackData; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ObjectMessage getAckMessage() { |  | ||||||
|         if (ackMessage == null) { |  | ||||||
|             ackMessage = Factory.createAck(this); |  | ||||||
|         } |  | ||||||
|         return ackMessage; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setInitialHash(byte[] initialHash) { |  | ||||||
|         this.initialHash = initialHash; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getInitialHash() { |  | ||||||
|         return initialHash; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         String subject = getSubject(); |  | ||||||
|         if (subject == null || subject.length() == 0) { |  | ||||||
|             return Strings.hex(initialHash).toString(); |  | ||||||
|         } else { |  | ||||||
|             return subject; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public enum Encoding { |  | ||||||
|         IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3); |  | ||||||
|  |  | ||||||
|         long code; |  | ||||||
|  |  | ||||||
|         Encoding(long code) { |  | ||||||
|             this.code = code; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public long getCode() { |  | ||||||
|             return code; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static Encoding fromCode(long code) { |  | ||||||
|             for (Encoding e : values()) { |  | ||||||
|                 if (e.getCode() == code) { |  | ||||||
|                     return e; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public enum Status { |  | ||||||
|         DRAFT, |  | ||||||
|         // For sent messages |  | ||||||
|         PUBKEY_REQUESTED, |  | ||||||
|         DOING_PROOF_OF_WORK, |  | ||||||
|         SENT, |  | ||||||
|         SENT_ACKNOWLEDGED, |  | ||||||
|         RECEIVED |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public enum Type { |  | ||||||
|         MSG, BROADCAST |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private Object id; |  | ||||||
|         private InventoryVector inventoryVector; |  | ||||||
|         private Type type; |  | ||||||
|         private BitmessageAddress from; |  | ||||||
|         private BitmessageAddress to; |  | ||||||
|         private long addressVersion; |  | ||||||
|         private long stream; |  | ||||||
|         private int behaviorBitfield; |  | ||||||
|         private byte[] publicSigningKey; |  | ||||||
|         private byte[] publicEncryptionKey; |  | ||||||
|         private long nonceTrialsPerByte; |  | ||||||
|         private long extraBytes; |  | ||||||
|         private byte[] destinationRipe; |  | ||||||
|         private long encoding; |  | ||||||
|         private byte[] message = new byte[0]; |  | ||||||
|         private byte[] ackData; |  | ||||||
|         private byte[] ackMessage; |  | ||||||
|         private byte[] signature; |  | ||||||
|         private Long sent; |  | ||||||
|         private Long received; |  | ||||||
|         private Status status; |  | ||||||
|         private Set<Label> labels = new LinkedHashSet<>(); |  | ||||||
|         private long ttl; |  | ||||||
|         private int retries; |  | ||||||
|         private Long nextTry; |  | ||||||
|         private UUID conversation; |  | ||||||
|  |  | ||||||
|         public Builder(Type type) { |  | ||||||
|             this.type = type; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder id(Object id) { |  | ||||||
|             this.id = id; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder IV(InventoryVector iv) { |  | ||||||
|             this.inventoryVector = iv; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder from(BitmessageAddress address) { |  | ||||||
|             from = address; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder to(BitmessageAddress address) { |  | ||||||
|             if (type != Type.MSG && to != null) |  | ||||||
|                 throw new IllegalArgumentException("recipient address only allowed for msg"); |  | ||||||
|             to = address; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder addressVersion(long addressVersion) { |  | ||||||
|             this.addressVersion = addressVersion; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder stream(long stream) { |  | ||||||
|             this.stream = stream; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder behaviorBitfield(int behaviorBitfield) { |  | ||||||
|             this.behaviorBitfield = behaviorBitfield; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder publicSigningKey(byte[] publicSigningKey) { |  | ||||||
|             this.publicSigningKey = publicSigningKey; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder publicEncryptionKey(byte[] publicEncryptionKey) { |  | ||||||
|             this.publicEncryptionKey = publicEncryptionKey; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder nonceTrialsPerByte(long nonceTrialsPerByte) { |  | ||||||
|             this.nonceTrialsPerByte = nonceTrialsPerByte; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder extraBytes(long extraBytes) { |  | ||||||
|             this.extraBytes = extraBytes; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder destinationRipe(byte[] ripe) { |  | ||||||
|             if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg"); |  | ||||||
|             this.destinationRipe = ripe; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder encoding(Encoding encoding) { |  | ||||||
|             this.encoding = encoding.getCode(); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder encoding(long encoding) { |  | ||||||
|             this.encoding = encoding; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder message(ExtendedEncoding message) { |  | ||||||
|             this.encoding = EXTENDED.getCode(); |  | ||||||
|             this.message = message.zip(); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder message(String subject, String message) { |  | ||||||
|             try { |  | ||||||
|                 this.encoding = SIMPLE.getCode(); |  | ||||||
|                 this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8"); |  | ||||||
|             } catch (UnsupportedEncodingException e) { |  | ||||||
|                 throw new ApplicationException(e); |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder message(byte[] message) { |  | ||||||
|             this.message = message; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder ackMessage(byte[] ack) { |  | ||||||
|             if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ackMessage only allowed for msg"); |  | ||||||
|             this.ackMessage = ack; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder ackData(byte[] ackData) { |  | ||||||
|             if (type != Type.MSG && ackData != null) |  | ||||||
|                 throw new IllegalArgumentException("ackMessage only allowed for msg"); |  | ||||||
|             this.ackData = ackData; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder signature(byte[] signature) { |  | ||||||
|             this.signature = signature; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder sent(Long sent) { |  | ||||||
|             this.sent = sent; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder received(Long received) { |  | ||||||
|             this.received = received; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder status(Status status) { |  | ||||||
|             this.status = status; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder labels(Collection<Label> labels) { |  | ||||||
|             this.labels.addAll(labels); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder ttl(long ttl) { |  | ||||||
|             this.ttl = ttl; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder retries(int retries) { |  | ||||||
|             this.retries = retries; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder nextTry(Long nextTry) { |  | ||||||
|             this.nextTry = nextTry; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder conversation(UUID id) { |  | ||||||
|             this.conversation = id; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Plaintext build() { |  | ||||||
|             if (from == null) { |  | ||||||
|                 from = new BitmessageAddress(Factory.createPubkey( |  | ||||||
|                     addressVersion, |  | ||||||
|                     stream, |  | ||||||
|                     publicSigningKey, |  | ||||||
|                     publicEncryptionKey, |  | ||||||
|                     nonceTrialsPerByte, |  | ||||||
|                     extraBytes, |  | ||||||
|                     behaviorBitfield |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|             if (to == null && type != Type.BROADCAST && destinationRipe != null) { |  | ||||||
|                 to = new BitmessageAddress(0, 0, destinationRipe); |  | ||||||
|             } |  | ||||||
|             if (type == Type.MSG && ackMessage == null && ackData == null) { |  | ||||||
|                 ackData = cryptography().randomBytes(Msg.ACK_LENGTH); |  | ||||||
|             } |  | ||||||
|             if (ttl <= 0) { |  | ||||||
|                 ttl = TTL.msg(); |  | ||||||
|             } |  | ||||||
|             if (conversation == null) { |  | ||||||
|                 conversation = UUID.randomUUID(); |  | ||||||
|             } |  | ||||||
|             return new Plaintext(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * An object that can be written to an {@link OutputStream} |  | ||||||
|  */ |  | ||||||
| public interface Streamable extends Serializable { |  | ||||||
|     void write(OutputStream stream) throws IOException; |  | ||||||
|  |  | ||||||
|     void write(ByteBuffer buffer); |  | ||||||
| } |  | ||||||
| @@ -1,246 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.BitmessageContext; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
| import ch.dissem.bitmessage.utils.UnixTime; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The 'version' command advertises this node's latest supported protocol version upon initiation. |  | ||||||
|  */ |  | ||||||
| public class Version implements MessagePayload { |  | ||||||
|     private static final long serialVersionUID = 7219240857343176567L; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's |  | ||||||
|      * version is lower but continue with the connection if it is higher. |  | ||||||
|      */ |  | ||||||
|     private final int version; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * bitfield of features to be enabled for this connection |  | ||||||
|      */ |  | ||||||
|     private final long services; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * standard UNIX timestamp in seconds |  | ||||||
|      */ |  | ||||||
|     private final long timestamp; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The network address of the node receiving this message (not including the time or stream number) |  | ||||||
|      */ |  | ||||||
|     private final NetworkAddress addrRecv; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The network address of the node emitting this message (not including the time or stream number and the ip itself |  | ||||||
|      * is ignored by the receiver) |  | ||||||
|      */ |  | ||||||
|     private final NetworkAddress addrFrom; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Random nonce used to detect connections to self. |  | ||||||
|      */ |  | ||||||
|     private final long nonce; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes. |  | ||||||
|      */ |  | ||||||
|     private final String userAgent; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000 |  | ||||||
|      * stream numbers. |  | ||||||
|      */ |  | ||||||
|     private final long[] streams; |  | ||||||
|  |  | ||||||
|     private Version(Builder builder) { |  | ||||||
|         version = builder.version; |  | ||||||
|         services = builder.services; |  | ||||||
|         timestamp = builder.timestamp; |  | ||||||
|         addrRecv = builder.addrRecv; |  | ||||||
|         addrFrom = builder.addrFrom; |  | ||||||
|         nonce = builder.nonce; |  | ||||||
|         userAgent = builder.userAgent; |  | ||||||
|         streams = builder.streamNumbers; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int getVersion() { |  | ||||||
|         return version; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getServices() { |  | ||||||
|         return services; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean provides(Service service) { |  | ||||||
|         return service != null && service.isEnabled(services); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getTimestamp() { |  | ||||||
|         return timestamp; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public NetworkAddress getAddrRecv() { |  | ||||||
|         return addrRecv; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public NetworkAddress getAddrFrom() { |  | ||||||
|         return addrFrom; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getNonce() { |  | ||||||
|         return nonce; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getUserAgent() { |  | ||||||
|         return userAgent; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long[] getStreams() { |  | ||||||
|         return streams; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Command getCommand() { |  | ||||||
|         return Command.VERSION; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream stream) throws IOException { |  | ||||||
|         Encode.int32(version, stream); |  | ||||||
|         Encode.int64(services, stream); |  | ||||||
|         Encode.int64(timestamp, stream); |  | ||||||
|         addrRecv.write(stream, true); |  | ||||||
|         addrFrom.write(stream, true); |  | ||||||
|         Encode.int64(nonce, stream); |  | ||||||
|         Encode.varString(userAgent, stream); |  | ||||||
|         Encode.varIntList(streams, stream); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         Encode.int32(version, buffer); |  | ||||||
|         Encode.int64(services, buffer); |  | ||||||
|         Encode.int64(timestamp, buffer); |  | ||||||
|         addrRecv.write(buffer, true); |  | ||||||
|         addrFrom.write(buffer, true); |  | ||||||
|         Encode.int64(nonce, buffer); |  | ||||||
|         Encode.varString(userAgent, buffer); |  | ||||||
|         Encode.varIntList(streams, buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private int version; |  | ||||||
|         private long services; |  | ||||||
|         private long timestamp; |  | ||||||
|         private NetworkAddress addrRecv; |  | ||||||
|         private NetworkAddress addrFrom; |  | ||||||
|         private long nonce; |  | ||||||
|         private String userAgent; |  | ||||||
|         private long[] streamNumbers; |  | ||||||
|  |  | ||||||
|         public Builder defaults(long clientNonce) { |  | ||||||
|             version = BitmessageContext.CURRENT_VERSION; |  | ||||||
|             services = Service.getServiceFlag(Service.NODE_NETWORK); |  | ||||||
|             timestamp = UnixTime.now(); |  | ||||||
|             userAgent = "/Jabit:0.0.1/"; |  | ||||||
|             streamNumbers = new long[]{1}; |  | ||||||
|             nonce = clientNonce; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder version(int version) { |  | ||||||
|             this.version = version; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder services(Service... services) { |  | ||||||
|             this.services = Service.getServiceFlag(services); |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder services(long services) { |  | ||||||
|             this.services = services; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder timestamp(long timestamp) { |  | ||||||
|             this.timestamp = timestamp; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addrRecv(NetworkAddress addrRecv) { |  | ||||||
|             this.addrRecv = addrRecv; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addrFrom(NetworkAddress addrFrom) { |  | ||||||
|             this.addrFrom = addrFrom; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder nonce(long nonce) { |  | ||||||
|             this.nonce = nonce; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder userAgent(String userAgent) { |  | ||||||
|             this.userAgent = userAgent; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder streams(long... streamNumbers) { |  | ||||||
|             this.streamNumbers = streamNumbers; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Version build() { |  | ||||||
|             return new Version(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public enum Service { |  | ||||||
|         NODE_NETWORK(1); |  | ||||||
| // TODO: NODE_SSL(2); |  | ||||||
|  |  | ||||||
|         long flag; |  | ||||||
|  |  | ||||||
|         Service(long flag) { |  | ||||||
|             this.flag = flag; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public boolean isEnabled(long flag) { |  | ||||||
|             return (flag & this.flag) != 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static long getServiceFlag(Service... services) { |  | ||||||
|             long flag = 0; |  | ||||||
|             for (Service service : services) { |  | ||||||
|                 flag |= service.flag; |  | ||||||
|             } |  | ||||||
|             return flag; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,114 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.Encrypted; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.PlaintextHolder; |  | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Users who are subscribed to the sending address will see the message appear in their inbox. |  | ||||||
|  * Broadcasts are version 4 or 5. |  | ||||||
|  */ |  | ||||||
| public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder { |  | ||||||
|     private static final long serialVersionUID = 4064521827582239069L; |  | ||||||
|  |  | ||||||
|     protected final long stream; |  | ||||||
|     protected CryptoBox encrypted; |  | ||||||
|     protected Plaintext plaintext; |  | ||||||
|  |  | ||||||
|     protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { |  | ||||||
|         super(version); |  | ||||||
|         this.stream = stream; |  | ||||||
|         this.encrypted = encrypted; |  | ||||||
|         this.plaintext = plaintext; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long getVersion(BitmessageAddress address) { |  | ||||||
|         return address.getVersion() < 4 ? 4 : 5; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean isSigned() { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getSignature() { |  | ||||||
|         return plaintext.getSignature(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setSignature(byte[] signature) { |  | ||||||
|         plaintext.setSignature(signature); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Plaintext getPlaintext() { |  | ||||||
|         return plaintext; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void encrypt(byte[] publicKey) throws IOException { |  | ||||||
|         this.encrypted = new CryptoBox(plaintext, publicKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void encrypt() throws IOException { |  | ||||||
|         encrypt(cryptography().createPublicKey(plaintext.getFrom().getPublicDecryptionKey())); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { |  | ||||||
|         plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException { |  | ||||||
|         decrypt(address.getPublicDecryptionKey()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean isDecrypted() { |  | ||||||
|         return plaintext != null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         Broadcast broadcast = (Broadcast) o; |  | ||||||
|         return stream == broadcast.stream && |  | ||||||
|                 (Objects.equals(encrypted, broadcast.encrypted) || Objects.equals(plaintext, broadcast.plaintext)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(stream); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,214 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.Streamable; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; |  | ||||||
| import ch.dissem.bitmessage.utils.*; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.*; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Arrays; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| public class CryptoBox implements Streamable { |  | ||||||
|     private static final long serialVersionUID = 7217659539975573852L; |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class); |  | ||||||
|  |  | ||||||
|     private final byte[] initializationVector; |  | ||||||
|     private final int curveType; |  | ||||||
|     private final byte[] R; |  | ||||||
|     private final byte[] mac; |  | ||||||
|     private byte[] encrypted; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public CryptoBox(Streamable data, byte[] K) throws IOException { |  | ||||||
|         this(Encode.bytes(data), K); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public CryptoBox(byte[] data, byte[] K) throws IOException { |  | ||||||
|         curveType = 0x02CA; |  | ||||||
|  |  | ||||||
|         // 1. The destination public key is called K. |  | ||||||
|         // 2. Generate 16 random bytes using a secure random number generator. Call them IV. |  | ||||||
|         initializationVector = cryptography().randomBytes(16); |  | ||||||
|  |  | ||||||
|         // 3. Generate a new random EC key pair with private key called r and public key called R. |  | ||||||
|         byte[] r = cryptography().randomBytes(PRIVATE_KEY_SIZE); |  | ||||||
|         R = cryptography().createPublicKey(r); |  | ||||||
|         // 4. Do an EC point multiply with public key K and private key r. This gives you public key P. |  | ||||||
|         byte[] P = cryptography().multiply(K, r); |  | ||||||
|         byte[] X = Points.getX(P); |  | ||||||
|         // 5. Use the X component of public key P and calculate the SHA512 hash H. |  | ||||||
|         byte[] H = cryptography().sha512(X); |  | ||||||
|         // 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. |  | ||||||
|         byte[] key_e = Arrays.copyOfRange(H, 0, 32); |  | ||||||
|         byte[] key_m = Arrays.copyOfRange(H, 32, 64); |  | ||||||
|         // 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. |  | ||||||
|         // 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text. |  | ||||||
|         encrypted = cryptography().crypt(true, data, key_e, initializationVector); |  | ||||||
|         // 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC. |  | ||||||
|         mac = calculateMac(key_m); |  | ||||||
|  |  | ||||||
|         // The resulting data is: IV + R + cipher text + MAC |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private CryptoBox(Builder builder) { |  | ||||||
|         initializationVector = builder.initializationVector; |  | ||||||
|         curveType = builder.curveType; |  | ||||||
|         R = cryptography().createPoint(builder.xComponent, builder.yComponent); |  | ||||||
|         encrypted = builder.encrypted; |  | ||||||
|         mac = builder.mac; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static CryptoBox read(InputStream stream, int length) throws IOException { |  | ||||||
|         AccessCounter counter = new AccessCounter(); |  | ||||||
|         return new Builder() |  | ||||||
|                 .IV(Decode.bytes(stream, 16, counter)) |  | ||||||
|                 .curveType(Decode.uint16(stream, counter)) |  | ||||||
|                 .X(Decode.shortVarBytes(stream, counter)) |  | ||||||
|                 .Y(Decode.shortVarBytes(stream, counter)) |  | ||||||
|                 .encrypted(Decode.bytes(stream, length - counter.length() - 32)) |  | ||||||
|                 .MAC(Decode.bytes(stream, 32)) |  | ||||||
|                 .build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @param k a private key, typically should be 32 bytes long |  | ||||||
|      * @return an InputStream yielding the decrypted data |  | ||||||
|      * @throws DecryptionFailedException if the payload can't be decrypted using this private key |  | ||||||
|      * @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a> |  | ||||||
|      */ |  | ||||||
|     public InputStream decrypt(byte[] k) throws DecryptionFailedException { |  | ||||||
|         // 1. The private key used to decrypt is called k. |  | ||||||
|         // 2. Do an EC point multiply with private key k and public key R. This gives you public key P. |  | ||||||
|         byte[] P = cryptography().multiply(R, k); |  | ||||||
|         // 3. Use the X component of public key P and calculate the SHA512 hash H. |  | ||||||
|         byte[] H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33)); |  | ||||||
|         // 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. |  | ||||||
|         byte[] key_e = Arrays.copyOfRange(H, 0, 32); |  | ||||||
|         byte[] key_m = Arrays.copyOfRange(H, 32, 64); |  | ||||||
|  |  | ||||||
|         // 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data. |  | ||||||
|         // 6. Compare MAC with MAC'. If not equal, decryption will fail. |  | ||||||
|         if (!Arrays.equals(mac, calculateMac(key_m))) { |  | ||||||
|             throw new DecryptionFailedException(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key |  | ||||||
|         //    and the cipher text as payload. The output is the padded input text. |  | ||||||
|         return new ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private byte[] calculateMac(byte[] key_m) { |  | ||||||
|         try { |  | ||||||
|             ByteArrayOutputStream macData = new ByteArrayOutputStream(); |  | ||||||
|             writeWithoutMAC(macData); |  | ||||||
|             return cryptography().mac(key_m, macData.toByteArray()); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void writeWithoutMAC(OutputStream out) throws IOException { |  | ||||||
|         out.write(initializationVector); |  | ||||||
|         Encode.int16(curveType, out); |  | ||||||
|         writeCoordinateComponent(out, Points.getX(R)); |  | ||||||
|         writeCoordinateComponent(out, Points.getY(R)); |  | ||||||
|         out.write(encrypted); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException { |  | ||||||
|         int offset = Bytes.numberOfLeadingZeros(x); |  | ||||||
|         int length = x.length - offset; |  | ||||||
|         Encode.int16(length, out); |  | ||||||
|         out.write(x, offset, length); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void writeCoordinateComponent(ByteBuffer buffer, byte[] x) { |  | ||||||
|         int offset = Bytes.numberOfLeadingZeros(x); |  | ||||||
|         int length = x.length - offset; |  | ||||||
|         Encode.int16(length, buffer); |  | ||||||
|         buffer.put(x, offset, length); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream stream) throws IOException { |  | ||||||
|         writeWithoutMAC(stream); |  | ||||||
|         stream.write(mac); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         buffer.put(initializationVector); |  | ||||||
|         Encode.int16(curveType, buffer); |  | ||||||
|         writeCoordinateComponent(buffer, Points.getX(R)); |  | ||||||
|         writeCoordinateComponent(buffer, Points.getY(R)); |  | ||||||
|         buffer.put(encrypted); |  | ||||||
|         buffer.put(mac); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private byte[] initializationVector; |  | ||||||
|         private int curveType; |  | ||||||
|         private byte[] xComponent; |  | ||||||
|         private byte[] yComponent; |  | ||||||
|         private byte[] encrypted; |  | ||||||
|         private byte[] mac; |  | ||||||
|  |  | ||||||
|         public Builder IV(byte[] initializationVector) { |  | ||||||
|             this.initializationVector = initializationVector; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder curveType(int curveType) { |  | ||||||
|             if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType); |  | ||||||
|             this.curveType = curveType; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder X(byte[] xComponent) { |  | ||||||
|             this.xComponent = xComponent; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder Y(byte[] yComponent) { |  | ||||||
|             this.yComponent = yComponent; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private Builder encrypted(byte[] encrypted) { |  | ||||||
|             this.encrypted = encrypted; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder MAC(byte[] mac) { |  | ||||||
|             this.mac = mac; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public CryptoBox build() { |  | ||||||
|             return new CryptoBox(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,88 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Arrays; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really |  | ||||||
|  * have to know what it is. |  | ||||||
|  */ |  | ||||||
| public class GenericPayload extends ObjectPayload { |  | ||||||
|     private static final long serialVersionUID = -912314085064185940L; |  | ||||||
|  |  | ||||||
|     private long stream; |  | ||||||
|     private byte[] data; |  | ||||||
|  |  | ||||||
|     public GenericPayload(long version, long stream, byte[] data) { |  | ||||||
|         super(version); |  | ||||||
|         this.stream = stream; |  | ||||||
|         this.data = data; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static GenericPayload read(long version, long stream, InputStream is, int length) throws IOException { |  | ||||||
|         return new GenericPayload(version, stream, Decode.bytes(is, length)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public ObjectType getType() { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getData() { |  | ||||||
|         return data; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream stream) throws IOException { |  | ||||||
|         stream.write(data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         buffer.put(data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|  |  | ||||||
|         GenericPayload that = (GenericPayload) o; |  | ||||||
|  |  | ||||||
|         if (stream != that.stream) return false; |  | ||||||
|         return Arrays.equals(data, that.data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         int result = (int) (stream ^ (stream >>> 32)); |  | ||||||
|         result = 31 * result + Arrays.hashCode(data); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Request for a public key. |  | ||||||
|  */ |  | ||||||
| public class GetPubkey extends ObjectPayload { |  | ||||||
|     private static final long serialVersionUID = -3634516646972610180L; |  | ||||||
|  |  | ||||||
|     private long stream; |  | ||||||
|     private byte[] ripeTag; |  | ||||||
|  |  | ||||||
|     public GetPubkey(BitmessageAddress address) { |  | ||||||
|         super(address.getVersion()); |  | ||||||
|         this.stream = address.getStream(); |  | ||||||
|         if (address.getVersion() < 4) |  | ||||||
|             this.ripeTag = address.getRipe(); |  | ||||||
|         else |  | ||||||
|             this.ripeTag = address.getTag(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private GetPubkey(long version, long stream, byte[] ripeOrTag) { |  | ||||||
|         super(version); |  | ||||||
|         this.stream = stream; |  | ||||||
|         this.ripeTag = ripeOrTag; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException { |  | ||||||
|         return new GetPubkey(version, stream, Decode.bytes(is, length)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @return an array of bytes that represent either the ripe, or the tag of an address, depending on the |  | ||||||
|      * address version. |  | ||||||
|      */ |  | ||||||
|     public byte[] getRipeTag() { |  | ||||||
|         return ripeTag; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public ObjectType getType() { |  | ||||||
|         return ObjectType.GET_PUBKEY; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream stream) throws IOException { |  | ||||||
|         stream.write(ripeTag); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         buffer.put(ripeTag); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,135 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.Encrypted; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.PlaintextHolder; |  | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Used for person-to-person messages. |  | ||||||
|  */ |  | ||||||
| public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder { |  | ||||||
|     private static final long serialVersionUID = 4327495048296365733L; |  | ||||||
|     public static final int ACK_LENGTH = 32; |  | ||||||
|  |  | ||||||
|     private long stream; |  | ||||||
|     private CryptoBox encrypted; |  | ||||||
|     private Plaintext plaintext; |  | ||||||
|  |  | ||||||
|     private Msg(long stream, CryptoBox encrypted) { |  | ||||||
|         super(1); |  | ||||||
|         this.stream = stream; |  | ||||||
|         this.encrypted = encrypted; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Msg(Plaintext plaintext) { |  | ||||||
|         super(1); |  | ||||||
|         this.stream = plaintext.getStream(); |  | ||||||
|         this.plaintext = plaintext; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static Msg read(InputStream in, long stream, int length) throws IOException { |  | ||||||
|         return new Msg(stream, CryptoBox.read(in, length)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Plaintext getPlaintext() { |  | ||||||
|         return plaintext; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public ObjectType getType() { |  | ||||||
|         return ObjectType.MSG; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean isSigned() { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void writeBytesToSign(OutputStream out) throws IOException { |  | ||||||
|         plaintext.write(out, false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getSignature() { |  | ||||||
|         return plaintext.getSignature(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setSignature(byte[] signature) { |  | ||||||
|         plaintext.setSignature(signature); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void encrypt(byte[] publicKey) throws IOException { |  | ||||||
|         this.encrypted = new CryptoBox(plaintext, publicKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { |  | ||||||
|         plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean isDecrypted() { |  | ||||||
|         return plaintext != null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it."); |  | ||||||
|         encrypted.write(out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it."); |  | ||||||
|         encrypted.write(buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         if (!super.equals(o)) return false; |  | ||||||
|         Msg msg = (Msg) o; |  | ||||||
|         return stream == msg.stream && |  | ||||||
|                 (Objects.equals(encrypted, msg.encrypted) || Objects.equals(plaintext, msg.plaintext)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return (int) stream; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,66 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.ObjectMessage; |  | ||||||
| import ch.dissem.bitmessage.entity.Streamable; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The payload of an 'object' command. This is shared by the network. |  | ||||||
|  */ |  | ||||||
| public abstract class ObjectPayload implements Streamable { |  | ||||||
|     private static final long serialVersionUID = -5034977402902364482L; |  | ||||||
|  |  | ||||||
|     private final long version; |  | ||||||
|  |  | ||||||
|     protected ObjectPayload(long version) { |  | ||||||
|         this.version = version; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public abstract ObjectType getType(); |  | ||||||
|  |  | ||||||
|     public abstract long getStream(); |  | ||||||
|  |  | ||||||
|     public long getVersion() { |  | ||||||
|         return version; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isSigned() { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void writeBytesToSign(OutputStream out) throws IOException { |  | ||||||
|         // nothing to do |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time, |  | ||||||
|      * appended with the data described in this table down to the extra_bytes. Therefore, this must |  | ||||||
|      * be checked and set in the {@link ObjectMessage} object. |  | ||||||
|      */ |  | ||||||
|     public byte[] getSignature() { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setSignature(byte[] signature) { |  | ||||||
|         // nothing to do |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,121 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.ArrayList; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Public keys for signing and encryption, the answer to a 'getpubkey' request. |  | ||||||
|  */ |  | ||||||
| public abstract class Pubkey extends ObjectPayload { |  | ||||||
|     private static final long serialVersionUID = -6634533361454999619L; |  | ||||||
|  |  | ||||||
|     public final static long LATEST_VERSION = 4; |  | ||||||
|  |  | ||||||
|     protected Pubkey(long version) { |  | ||||||
|         super(version); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) { |  | ||||||
|         return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public abstract byte[] getSigningKey(); |  | ||||||
|  |  | ||||||
|     public abstract byte[] getEncryptionKey(); |  | ||||||
|  |  | ||||||
|     public abstract int getBehaviorBitfield(); |  | ||||||
|  |  | ||||||
|     public byte[] getRipe() { |  | ||||||
|         return cryptography().ripemd160(cryptography().sha512(getSigningKey(), getEncryptionKey())); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getNonceTrialsPerByte() { |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getExtraBytes() { |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void writeUnencrypted(OutputStream out) throws IOException { |  | ||||||
|         write(out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void writeUnencrypted(ByteBuffer buffer){ |  | ||||||
|         write(buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected byte[] add0x04(byte[] key) { |  | ||||||
|         if (key.length == 65) return key; |  | ||||||
|         byte[] result = new byte[65]; |  | ||||||
|         result[0] = 4; |  | ||||||
|         System.arraycopy(key, 0, result, 1, 64); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Bits 0 through 29 are yet undefined |  | ||||||
|      */ |  | ||||||
|     public enum Feature { |  | ||||||
|         /** |  | ||||||
|          * Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg |  | ||||||
|          * messages bound for them. |  | ||||||
|          */ |  | ||||||
|         INCLUDE_DESTINATION(30), |  | ||||||
|         /** |  | ||||||
|          * If true, the receiving node does send acknowledgements (rather than dropping them). |  | ||||||
|          */ |  | ||||||
|         DOES_ACK(31); |  | ||||||
|  |  | ||||||
|         private int bit; |  | ||||||
|  |  | ||||||
|         Feature(int bitNumber) { |  | ||||||
|             // The Bitmessage Protocol Specification starts counting at the most significant bit, |  | ||||||
|             // thus the slightly awkward calculation. |  | ||||||
|             // https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features |  | ||||||
|             this.bit = 1 << (31 - bitNumber); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static int bitfield(Feature... features) { |  | ||||||
|             int bits = 0; |  | ||||||
|             for (Feature feature : features) { |  | ||||||
|                 bits |= feature.bit; |  | ||||||
|             } |  | ||||||
|             return bits; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public static Feature[] features(int bitfield) { |  | ||||||
|             ArrayList<Feature> features = new ArrayList<>(Feature.values().length); |  | ||||||
|             for (Feature feature : Feature.values()) { |  | ||||||
|                 if ((bitfield & feature.bit) != 0) { |  | ||||||
|                     features.add(feature); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return features.toArray(new Feature[features.size()]); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public boolean isActive(int bitfield) { |  | ||||||
|             return (bitfield & bit) != 0; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,133 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A version 2 public key. |  | ||||||
|  */ |  | ||||||
| public class V2Pubkey extends Pubkey { |  | ||||||
|     private static final long serialVersionUID = -257598690676510460L; |  | ||||||
|  |  | ||||||
|     protected long stream; |  | ||||||
|     protected int behaviorBitfield; |  | ||||||
|     protected byte[] publicSigningKey; // 64 Bytes |  | ||||||
|     protected byte[] publicEncryptionKey; // 64 Bytes |  | ||||||
|  |  | ||||||
|     protected V2Pubkey(long version) { |  | ||||||
|         super(version); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private V2Pubkey(long version, Builder builder) { |  | ||||||
|         super(version); |  | ||||||
|         stream = builder.streamNumber; |  | ||||||
|         behaviorBitfield = builder.behaviorBitfield; |  | ||||||
|         publicSigningKey = add0x04(builder.publicSigningKey); |  | ||||||
|         publicEncryptionKey = add0x04(builder.publicEncryptionKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static V2Pubkey read(InputStream is, long stream) throws IOException { |  | ||||||
|         return new V2Pubkey.Builder() |  | ||||||
|                 .stream(stream) |  | ||||||
|                 .behaviorBitfield((int) Decode.uint32(is)) |  | ||||||
|                 .publicSigningKey(Decode.bytes(is, 64)) |  | ||||||
|                 .publicEncryptionKey(Decode.bytes(is, 64)) |  | ||||||
|                 .build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getVersion() { |  | ||||||
|         return 2; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public ObjectType getType() { |  | ||||||
|         return ObjectType.PUBKEY; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getSigningKey() { |  | ||||||
|         return publicSigningKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getEncryptionKey() { |  | ||||||
|         return publicEncryptionKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int getBehaviorBitfield() { |  | ||||||
|         return behaviorBitfield; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         Encode.int32(behaviorBitfield, out); |  | ||||||
|         out.write(publicSigningKey, 1, 64); |  | ||||||
|         out.write(publicEncryptionKey, 1, 64); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         Encode.int32(behaviorBitfield, buffer); |  | ||||||
|         buffer.put(publicSigningKey, 1, 64); |  | ||||||
|         buffer.put(publicEncryptionKey, 1, 64); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Builder { |  | ||||||
|         private long streamNumber; |  | ||||||
|         private int behaviorBitfield; |  | ||||||
|         private byte[] publicSigningKey; |  | ||||||
|         private byte[] publicEncryptionKey; |  | ||||||
|  |  | ||||||
|         public Builder stream(long streamNumber) { |  | ||||||
|             this.streamNumber = streamNumber; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder behaviorBitfield(int behaviorBitfield) { |  | ||||||
|             this.behaviorBitfield = behaviorBitfield; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder publicSigningKey(byte[] publicSigningKey) { |  | ||||||
|             this.publicSigningKey = publicSigningKey; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder publicEncryptionKey(byte[] publicEncryptionKey) { |  | ||||||
|             this.publicEncryptionKey = publicEncryptionKey; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public V2Pubkey build() { |  | ||||||
|             return new V2Pubkey(2, this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,175 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A version 3 public key. |  | ||||||
|  */ |  | ||||||
| public class V3Pubkey extends V2Pubkey { |  | ||||||
|     private static final long serialVersionUID = 6958853116648528319L; |  | ||||||
|  |  | ||||||
|     long nonceTrialsPerByte; |  | ||||||
|     long extraBytes; |  | ||||||
|     byte[] signature; |  | ||||||
|  |  | ||||||
|     protected V3Pubkey(long version, Builder builder) { |  | ||||||
|         super(version); |  | ||||||
|         stream = builder.streamNumber; |  | ||||||
|         behaviorBitfield = builder.behaviorBitfield; |  | ||||||
|         publicSigningKey = add0x04(builder.publicSigningKey); |  | ||||||
|         publicEncryptionKey = add0x04(builder.publicEncryptionKey); |  | ||||||
|         nonceTrialsPerByte = builder.nonceTrialsPerByte; |  | ||||||
|         extraBytes = builder.extraBytes; |  | ||||||
|         signature = builder.signature; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static V3Pubkey read(InputStream is, long stream) throws IOException { |  | ||||||
|         return new V3Pubkey.Builder() |  | ||||||
|             .stream(stream) |  | ||||||
|             .behaviorBitfield(Decode.int32(is)) |  | ||||||
|             .publicSigningKey(Decode.bytes(is, 64)) |  | ||||||
|             .publicEncryptionKey(Decode.bytes(is, 64)) |  | ||||||
|             .nonceTrialsPerByte(Decode.varInt(is)) |  | ||||||
|             .extraBytes(Decode.varInt(is)) |  | ||||||
|             .signature(Decode.varBytes(is)) |  | ||||||
|             .build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         writeBytesToSign(out); |  | ||||||
|         Encode.varBytes(signature, out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         super.write(buffer); |  | ||||||
|         Encode.varInt(nonceTrialsPerByte, buffer); |  | ||||||
|         Encode.varInt(extraBytes, buffer); |  | ||||||
|         Encode.varBytes(signature, buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getVersion() { |  | ||||||
|         return 3; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getNonceTrialsPerByte() { |  | ||||||
|         return nonceTrialsPerByte; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getExtraBytes() { |  | ||||||
|         return extraBytes; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean isSigned() { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void writeBytesToSign(OutputStream out) throws IOException { |  | ||||||
|         super.write(out); |  | ||||||
|         Encode.varInt(nonceTrialsPerByte, out); |  | ||||||
|         Encode.varInt(extraBytes, out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getSignature() { |  | ||||||
|         return signature; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setSignature(byte[] signature) { |  | ||||||
|         this.signature = signature; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         V3Pubkey pubkey = (V3Pubkey) o; |  | ||||||
|         return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) && |  | ||||||
|             Objects.equals(extraBytes, pubkey.extraBytes) && |  | ||||||
|             stream == pubkey.stream && |  | ||||||
|             behaviorBitfield == pubkey.behaviorBitfield && |  | ||||||
|             Arrays.equals(publicSigningKey, pubkey.publicSigningKey) && |  | ||||||
|             Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(nonceTrialsPerByte, extraBytes); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Builder { |  | ||||||
|         private long streamNumber; |  | ||||||
|         private int behaviorBitfield; |  | ||||||
|         private byte[] publicSigningKey; |  | ||||||
|         private byte[] publicEncryptionKey; |  | ||||||
|         private long nonceTrialsPerByte; |  | ||||||
|         private long extraBytes; |  | ||||||
|         private byte[] signature = new byte[0]; |  | ||||||
|  |  | ||||||
|         public Builder stream(long streamNumber) { |  | ||||||
|             this.streamNumber = streamNumber; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder behaviorBitfield(int behaviorBitfield) { |  | ||||||
|             this.behaviorBitfield = behaviorBitfield; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder publicSigningKey(byte[] publicSigningKey) { |  | ||||||
|             this.publicSigningKey = publicSigningKey; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder publicEncryptionKey(byte[] publicEncryptionKey) { |  | ||||||
|             this.publicEncryptionKey = publicEncryptionKey; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder nonceTrialsPerByte(long nonceTrialsPerByte) { |  | ||||||
|             this.nonceTrialsPerByte = nonceTrialsPerByte; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder extraBytes(long extraBytes) { |  | ||||||
|             this.extraBytes = extraBytes; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder signature(byte[] signature) { |  | ||||||
|             this.signature = signature; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public V3Pubkey build() { |  | ||||||
|             return new V3Pubkey(3, this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,67 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Users who are subscribed to the sending address will see the message appear in their inbox. |  | ||||||
|  * Broadcasts are version 4 or 5. |  | ||||||
|  */ |  | ||||||
| public class V4Broadcast extends Broadcast { |  | ||||||
|     private static final long serialVersionUID = 195663108282762711L; |  | ||||||
|  |  | ||||||
|     protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { |  | ||||||
|         super(version, stream, encrypted, plaintext); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { |  | ||||||
|         super(4, senderAddress.getStream(), null, plaintext); |  | ||||||
|         if (senderAddress.getVersion() >= 4) |  | ||||||
|             throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static V4Broadcast read(InputStream in, long stream, int length) throws IOException { |  | ||||||
|         return new V4Broadcast(4, stream, CryptoBox.read(in, length), null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public ObjectType getType() { |  | ||||||
|         return ObjectType.BROADCAST; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void writeBytesToSign(OutputStream out) throws IOException { |  | ||||||
|         plaintext.write(out, false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         encrypted.write(out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         encrypted.write(buffer); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,191 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.Encrypted; |  | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Arrays; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is |  | ||||||
|  * done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and |  | ||||||
|  * use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them |  | ||||||
|  * to create messages to be used in spam or in flooding attacks. |  | ||||||
|  */ |  | ||||||
| public class V4Pubkey extends Pubkey implements Encrypted { |  | ||||||
|     private static final long serialVersionUID = 1556710353694033093L; |  | ||||||
|  |  | ||||||
|     private long stream; |  | ||||||
|     private byte[] tag; |  | ||||||
|     private CryptoBox encrypted; |  | ||||||
|     private V3Pubkey decrypted; |  | ||||||
|  |  | ||||||
|     private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) { |  | ||||||
|         super(4); |  | ||||||
|         this.stream = stream; |  | ||||||
|         this.tag = tag; |  | ||||||
|         this.encrypted = encrypted; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public V4Pubkey(V3Pubkey decrypted) { |  | ||||||
|         super(4); |  | ||||||
|         this.decrypted = decrypted; |  | ||||||
|         this.stream = decrypted.stream; |  | ||||||
|         this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException { |  | ||||||
|         if (encrypted) |  | ||||||
|             return new V4Pubkey(stream, |  | ||||||
|                     Decode.bytes(in, 32), |  | ||||||
|                     CryptoBox.read(in, length - 32)); |  | ||||||
|         else |  | ||||||
|             return new V4Pubkey(V3Pubkey.read(in, stream)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void encrypt(byte[] publicKey) throws IOException { |  | ||||||
|         if (getSignature() == null) throw new IllegalStateException("Pubkey must be signed before encryption."); |  | ||||||
|         this.encrypted = new CryptoBox(decrypted, publicKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { |  | ||||||
|         decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean isDecrypted() { |  | ||||||
|         return decrypted != null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream stream) throws IOException { |  | ||||||
|         stream.write(tag); |  | ||||||
|         encrypted.write(stream); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         buffer.put(tag); |  | ||||||
|         encrypted.write(buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void writeUnencrypted(OutputStream out) throws IOException { |  | ||||||
|         decrypted.write(out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void writeUnencrypted(ByteBuffer buffer) { |  | ||||||
|         decrypted.write(buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void writeBytesToSign(OutputStream out) throws IOException { |  | ||||||
|         out.write(tag); |  | ||||||
|         decrypted.writeBytesToSign(out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getVersion() { |  | ||||||
|         return 4; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public ObjectType getType() { |  | ||||||
|         return ObjectType.PUBKEY; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getTag() { |  | ||||||
|         return tag; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getSigningKey() { |  | ||||||
|         return decrypted.getSigningKey(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getEncryptionKey() { |  | ||||||
|         return decrypted.getEncryptionKey(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int getBehaviorBitfield() { |  | ||||||
|         return decrypted.getBehaviorBitfield(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getSignature() { |  | ||||||
|         if (decrypted != null) |  | ||||||
|             return decrypted.getSignature(); |  | ||||||
|         else |  | ||||||
|             return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setSignature(byte[] signature) { |  | ||||||
|         decrypted.setSignature(signature); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean isSigned() { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getNonceTrialsPerByte() { |  | ||||||
|         return decrypted.getNonceTrialsPerByte(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getExtraBytes() { |  | ||||||
|         return decrypted.getExtraBytes(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|  |  | ||||||
|         V4Pubkey v4Pubkey = (V4Pubkey) o; |  | ||||||
|  |  | ||||||
|         if (stream != v4Pubkey.stream) return false; |  | ||||||
|         if (!Arrays.equals(tag, v4Pubkey.tag)) return false; |  | ||||||
|         return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null); |  | ||||||
|  |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         int result = (int) (stream ^ (stream >>> 32)); |  | ||||||
|         result = 31 * result + Arrays.hashCode(tag); |  | ||||||
|         result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,66 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.payload; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.OutputStream; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Users who are subscribed to the sending address will see the message appear in their inbox. |  | ||||||
|  */ |  | ||||||
| public class V5Broadcast extends V4Broadcast { |  | ||||||
|     private static final long serialVersionUID = 920649721626968644L; |  | ||||||
|  |  | ||||||
|     private byte[] tag; |  | ||||||
|  |  | ||||||
|     private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) { |  | ||||||
|         super(5, stream, encrypted, null); |  | ||||||
|         this.tag = tag; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { |  | ||||||
|         super(5, senderAddress.getStream(), null, plaintext); |  | ||||||
|         if (senderAddress.getVersion() < 4) |  | ||||||
|             throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion()); |  | ||||||
|         this.tag = senderAddress.getTag(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static V5Broadcast read(InputStream is, long stream, int length) throws IOException { |  | ||||||
|         return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getTag() { |  | ||||||
|         return tag; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void writeBytesToSign(OutputStream out) throws IOException { |  | ||||||
|         out.write(tag); |  | ||||||
|         super.writeBytesToSign(out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         out.write(tag); |  | ||||||
|         super.write(out); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,76 +0,0 @@ | |||||||
| package ch.dissem.bitmessage.entity.valueobject; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.msgpack.types.MPMap; |  | ||||||
| import ch.dissem.msgpack.types.MPString; |  | ||||||
| import ch.dissem.msgpack.types.MPType; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayOutputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.util.Objects; |  | ||||||
| import java.util.zip.DeflaterOutputStream; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Extended encoding message object. |  | ||||||
|  */ |  | ||||||
| public class ExtendedEncoding implements Serializable { |  | ||||||
|     private static final long serialVersionUID = 3876871488247305200L; |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncoding.class); |  | ||||||
|  |  | ||||||
|     private ExtendedType content; |  | ||||||
|  |  | ||||||
|     public ExtendedEncoding(ExtendedType content) { |  | ||||||
|         this.content = content; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getType() { |  | ||||||
|         if (content == null) { |  | ||||||
|             return null; |  | ||||||
|         } else { |  | ||||||
|             return content.getType(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ExtendedType getContent() { |  | ||||||
|         return content; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] zip() { |  | ||||||
|         try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { |  | ||||||
|             try (DeflaterOutputStream zipper = new DeflaterOutputStream(out)) { |  | ||||||
|                 content.pack().pack(zipper); |  | ||||||
|             } |  | ||||||
|             return out.toByteArray(); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         ExtendedEncoding that = (ExtendedEncoding) o; |  | ||||||
|         return Objects.equals(content, that.content); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(content); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public interface Unpacker<T extends ExtendedType> { |  | ||||||
|         String getType(); |  | ||||||
|  |  | ||||||
|         T unpack(MPMap<MPString, MPType<?>> map); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public interface ExtendedType extends Serializable { |  | ||||||
|         String getType(); |  | ||||||
|  |  | ||||||
|         MPMap<MPString, MPType<?>> pack() throws IOException; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.valueobject; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.Streamable; |  | ||||||
| import ch.dissem.bitmessage.utils.Strings; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Arrays; |  | ||||||
|  |  | ||||||
| public class InventoryVector implements Streamable, Serializable { |  | ||||||
|     private static final long serialVersionUID = -7349009673063348719L; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Hash of the object |  | ||||||
|      */ |  | ||||||
|     private final byte[] hash; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (!(o instanceof InventoryVector)) return false; |  | ||||||
|  |  | ||||||
|         InventoryVector that = (InventoryVector) o; |  | ||||||
|  |  | ||||||
|         return Arrays.equals(hash, that.hash); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return hash == null ? 0 : Arrays.hashCode(hash); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getHash() { |  | ||||||
|         return hash; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private InventoryVector(byte[] hash) { |  | ||||||
|         if (hash == null) throw new IllegalArgumentException("hash must not be null"); |  | ||||||
|         this.hash = hash; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static InventoryVector fromHash(byte[] hash) { |  | ||||||
|         if (hash == null) { |  | ||||||
|             return null; |  | ||||||
|         } else { |  | ||||||
|             return new InventoryVector(hash); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         out.write(hash); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         buffer.put(hash); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         return Strings.hex(hash).toString(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,89 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.valueobject; |  | ||||||
|  |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| public class Label implements Serializable { |  | ||||||
|     private static final long serialVersionUID = 831782893630994914L; |  | ||||||
|  |  | ||||||
|     private Object id; |  | ||||||
|     private String label; |  | ||||||
|     private Type type; |  | ||||||
|     private int color; |  | ||||||
|  |  | ||||||
|     public Label(String label, Type type, int color) { |  | ||||||
|         this.label = label; |  | ||||||
|         this.type = type; |  | ||||||
|         this.color = color; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @return RGBA representation for the color. |  | ||||||
|      */ |  | ||||||
|     public int getColor() { |  | ||||||
|         return color; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @param color RGBA representation for the color. |  | ||||||
|      */ |  | ||||||
|     public void setColor(int color) { |  | ||||||
|         this.color = color; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         return label; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Object getId() { |  | ||||||
|         return id; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setId(Object id) { |  | ||||||
|         this.id = id; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Type getType() { |  | ||||||
|         return type; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         Label label1 = (Label) o; |  | ||||||
|         return Objects.equals(label, label1.label); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(label); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public enum Type { |  | ||||||
|         INBOX, |  | ||||||
|         BROADCAST, |  | ||||||
|         DRAFT, |  | ||||||
|         OUTBOX, |  | ||||||
|         SENT, |  | ||||||
|         UNREAD, |  | ||||||
|         TRASH |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,246 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.valueobject; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.Streamable; |  | ||||||
| import ch.dissem.bitmessage.entity.Version; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
| import ch.dissem.bitmessage.utils.UnixTime; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.net.InetAddress; |  | ||||||
| import java.net.InetSocketAddress; |  | ||||||
| import java.net.SocketAddress; |  | ||||||
| import java.net.UnknownHostException; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Arrays; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A node's address. It's written in IPv6 format. |  | ||||||
|  */ |  | ||||||
| public class NetworkAddress implements Streamable { |  | ||||||
|     private static final long serialVersionUID = 2500120578167100300L; |  | ||||||
|  |  | ||||||
|     private long time; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Stream number for this node |  | ||||||
|      */ |  | ||||||
|     private final long stream; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * same service(s) listed in version |  | ||||||
|      */ |  | ||||||
|     private final long services; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address |  | ||||||
|      * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). |  | ||||||
|      */ |  | ||||||
|     private final byte[] ipv6; |  | ||||||
|     private final int port; |  | ||||||
|  |  | ||||||
|     private NetworkAddress(Builder builder) { |  | ||||||
|         time = builder.time; |  | ||||||
|         stream = builder.stream; |  | ||||||
|         services = builder.services; |  | ||||||
|         ipv6 = builder.ipv6; |  | ||||||
|         port = builder.port; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getIPv6() { |  | ||||||
|         return ipv6; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int getPort() { |  | ||||||
|         return port; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getServices() { |  | ||||||
|         return services; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public boolean provides(Version.Service service) { |  | ||||||
|         if (service == null) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         return service.isEnabled(services); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getStream() { |  | ||||||
|         return stream; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long getTime() { |  | ||||||
|         return time; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void setTime(long time) { |  | ||||||
|         this.time = time; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public InetAddress toInetAddress() { |  | ||||||
|         try { |  | ||||||
|             return InetAddress.getByAddress(ipv6); |  | ||||||
|         } catch (UnknownHostException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|  |  | ||||||
|         NetworkAddress that = (NetworkAddress) o; |  | ||||||
|  |  | ||||||
|         return port == that.port && Arrays.equals(ipv6, that.ipv6); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         int result = ipv6 != null ? Arrays.hashCode(ipv6) : 0; |  | ||||||
|         result = 31 * result + port; |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         return "[" + toInetAddress() + "]:" + port; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream stream) throws IOException { |  | ||||||
|         write(stream, false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void write(OutputStream out, boolean light) throws IOException { |  | ||||||
|         if (!light) { |  | ||||||
|             Encode.int64(time, out); |  | ||||||
|             Encode.int32(stream, out); |  | ||||||
|         } |  | ||||||
|         Encode.int64(services, out); |  | ||||||
|         out.write(ipv6); |  | ||||||
|         Encode.int16(port, out); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         write(buffer, false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void write(ByteBuffer buffer, boolean light) { |  | ||||||
|         if (!light) { |  | ||||||
|             Encode.int64(time, buffer); |  | ||||||
|             Encode.int32(stream, buffer); |  | ||||||
|         } |  | ||||||
|         Encode.int64(services, buffer); |  | ||||||
|         buffer.put(ipv6); |  | ||||||
|         Encode.int16(port, buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private long time; |  | ||||||
|         private long stream; |  | ||||||
|         private long services = 1; |  | ||||||
|         private byte[] ipv6; |  | ||||||
|         private int port; |  | ||||||
|  |  | ||||||
|         public Builder time(final long time) { |  | ||||||
|             this.time = time; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder stream(final long stream) { |  | ||||||
|             this.stream = stream; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder services(final long services) { |  | ||||||
|             this.services = services; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder ip(InetAddress inetAddress) { |  | ||||||
|             byte[] addr = inetAddress.getAddress(); |  | ||||||
|             if (addr.length == 16) { |  | ||||||
|                 this.ipv6 = addr; |  | ||||||
|             } else if (addr.length == 4) { |  | ||||||
|                 this.ipv6 = new byte[16]; |  | ||||||
|                 this.ipv6[10] = (byte) 0xff; |  | ||||||
|                 this.ipv6[11] = (byte) 0xff; |  | ||||||
|                 System.arraycopy(addr, 0, this.ipv6, 12, 4); |  | ||||||
|             } else { |  | ||||||
|                 throw new IllegalArgumentException("Weird address " + inetAddress); |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder ipv6(byte[] ipv6) { |  | ||||||
|             this.ipv6 = ipv6; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder ipv6(int p00, int p01, int p02, int p03, |  | ||||||
|                             int p04, int p05, int p06, int p07, |  | ||||||
|                             int p08, int p09, int p10, int p11, |  | ||||||
|                             int p12, int p13, int p14, int p15) { |  | ||||||
|             this.ipv6 = new byte[]{ |  | ||||||
|                 (byte) p00, (byte) p01, (byte) p02, (byte) p03, |  | ||||||
|                 (byte) p04, (byte) p05, (byte) p06, (byte) p07, |  | ||||||
|                 (byte) p08, (byte) p09, (byte) p10, (byte) p11, |  | ||||||
|                 (byte) p12, (byte) p13, (byte) p14, (byte) p15 |  | ||||||
|             }; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder ipv4(int p00, int p01, int p02, int p03) { |  | ||||||
|             this.ipv6 = new byte[]{ |  | ||||||
|                 (byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00, |  | ||||||
|                 (byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00, |  | ||||||
|                 (byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff, |  | ||||||
|                 (byte) p00, (byte) p01, (byte) p02, (byte) p03 |  | ||||||
|             }; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder port(final int port) { |  | ||||||
|             this.port = port; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder address(SocketAddress address) { |  | ||||||
|             if (address instanceof InetSocketAddress) { |  | ||||||
|                 InetSocketAddress inetAddress = (InetSocketAddress) address; |  | ||||||
|                 ip(inetAddress.getAddress()); |  | ||||||
|                 port(inetAddress.getPort()); |  | ||||||
|             } else { |  | ||||||
|                 throw new IllegalArgumentException("Unknown type of address: " + address.getClass()); |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public NetworkAddress build() { |  | ||||||
|             if (time == 0) { |  | ||||||
|                 time = UnixTime.now(); |  | ||||||
|             } |  | ||||||
|             return new NetworkAddress(this); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,198 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.entity.valueobject; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.InternalContext; |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.Streamable; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.factory.Factory; |  | ||||||
| import ch.dissem.bitmessage.utils.Bytes; |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
| import ch.dissem.bitmessage.utils.Encode; |  | ||||||
|  |  | ||||||
| import java.io.*; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying |  | ||||||
|  * {@link Pubkey} object. |  | ||||||
|  */ |  | ||||||
| public class PrivateKey implements Streamable { |  | ||||||
|     private static final long serialVersionUID = 8562555470709110558L; |  | ||||||
|  |  | ||||||
|     public static final int PRIVATE_KEY_SIZE = 32; |  | ||||||
|  |  | ||||||
|     private final byte[] privateSigningKey; |  | ||||||
|     private final byte[] privateEncryptionKey; |  | ||||||
|  |  | ||||||
|     private final Pubkey pubkey; |  | ||||||
|  |  | ||||||
|     public PrivateKey(boolean shorter, long stream, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { |  | ||||||
|         byte[] privSK; |  | ||||||
|         byte[] pubSK; |  | ||||||
|         byte[] privEK; |  | ||||||
|         byte[] pubEK; |  | ||||||
|         byte[] ripe; |  | ||||||
|         do { |  | ||||||
|             privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE); |  | ||||||
|             privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE); |  | ||||||
|             pubSK = cryptography().createPublicKey(privSK); |  | ||||||
|             pubEK = cryptography().createPublicKey(privEK); |  | ||||||
|             ripe = Pubkey.getRipe(pubSK, pubEK); |  | ||||||
|         } while (ripe[0] != 0 || (shorter && ripe[1] != 0)); |  | ||||||
|         this.privateSigningKey = privSK; |  | ||||||
|         this.privateEncryptionKey = privEK; |  | ||||||
|         this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, |  | ||||||
|                 nonceTrialsPerByte, extraBytes, features); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) { |  | ||||||
|         this.privateSigningKey = privateSigningKey; |  | ||||||
|         this.privateEncryptionKey = privateEncryptionKey; |  | ||||||
|         this.pubkey = pubkey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public PrivateKey(BitmessageAddress address, String passphrase) { |  | ||||||
|         this(address.getVersion(), address.getStream(), passphrase); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public PrivateKey(long version, long stream, String passphrase) { |  | ||||||
|         this(new Builder(version, stream, false).seed(passphrase).generate()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private PrivateKey(Builder builder) { |  | ||||||
|         this.privateSigningKey = builder.privSK; |  | ||||||
|         this.privateEncryptionKey = builder.privEK; |  | ||||||
|         this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK, builder.pubEK, |  | ||||||
|                 InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static class Builder { |  | ||||||
|         final long version; |  | ||||||
|         final long stream; |  | ||||||
|         final boolean shorter; |  | ||||||
|  |  | ||||||
|         byte[] seed; |  | ||||||
|         long nextNonce; |  | ||||||
|  |  | ||||||
|         byte[] privSK, privEK; |  | ||||||
|         byte[] pubSK, pubEK; |  | ||||||
|  |  | ||||||
|         private Builder(long version, long stream, boolean shorter) { |  | ||||||
|             this.version = version; |  | ||||||
|             this.stream = stream; |  | ||||||
|             this.shorter = shorter; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Builder seed(String passphrase) { |  | ||||||
|             try { |  | ||||||
|                 seed = passphrase.getBytes("UTF-8"); |  | ||||||
|             } catch (UnsupportedEncodingException e) { |  | ||||||
|                 throw new ApplicationException(e); |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Builder generate() { |  | ||||||
|             long signingKeyNonce = nextNonce; |  | ||||||
|             long encryptionKeyNonce = nextNonce + 1; |  | ||||||
|             byte[] ripe; |  | ||||||
|             do { |  | ||||||
|                 privEK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32); |  | ||||||
|                 privSK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(signingKeyNonce)), 32); |  | ||||||
|                 pubSK = cryptography().createPublicKey(privSK); |  | ||||||
|                 pubEK = cryptography().createPublicKey(privEK); |  | ||||||
|                 ripe = cryptography().ripemd160(cryptography().sha512(pubSK, pubEK)); |  | ||||||
|  |  | ||||||
|                 signingKeyNonce += 2; |  | ||||||
|                 encryptionKeyNonce += 2; |  | ||||||
|             } while (ripe[0] != 0 || (shorter && ripe[1] != 0)); |  | ||||||
|             nextNonce = signingKeyNonce; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static List<PrivateKey> deterministic(String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { |  | ||||||
|         List<PrivateKey> result = new ArrayList<>(numberOfAddresses); |  | ||||||
|         Builder builder = new Builder(version, stream, shorter).seed(passphrase); |  | ||||||
|         for (int i = 0; i < numberOfAddresses; i++) { |  | ||||||
|             builder.generate(); |  | ||||||
|             result.add(new PrivateKey(builder)); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static PrivateKey read(InputStream is) throws IOException { |  | ||||||
|         int version = (int) Decode.varInt(is); |  | ||||||
|         long stream = Decode.varInt(is); |  | ||||||
|         int len = (int) Decode.varInt(is); |  | ||||||
|         Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false); |  | ||||||
|         len = (int) Decode.varInt(is); |  | ||||||
|         byte[] signingKey = Decode.bytes(is, len); |  | ||||||
|         len = (int) Decode.varInt(is); |  | ||||||
|         byte[] encryptionKey = Decode.bytes(is, len); |  | ||||||
|         return new PrivateKey(signingKey, encryptionKey, pubkey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getPrivateSigningKey() { |  | ||||||
|         return privateSigningKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getPrivateEncryptionKey() { |  | ||||||
|         return privateEncryptionKey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Pubkey getPubkey() { |  | ||||||
|         return pubkey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(OutputStream out) throws IOException { |  | ||||||
|         Encode.varInt(pubkey.getVersion(), out); |  | ||||||
|         Encode.varInt(pubkey.getStream(), out); |  | ||||||
|         ByteArrayOutputStream baos = new ByteArrayOutputStream(); |  | ||||||
|         pubkey.writeUnencrypted(baos); |  | ||||||
|         Encode.varInt(baos.size(), out); |  | ||||||
|         out.write(baos.toByteArray()); |  | ||||||
|         Encode.varInt(privateSigningKey.length, out); |  | ||||||
|         out.write(privateSigningKey); |  | ||||||
|         Encode.varInt(privateEncryptionKey.length, out); |  | ||||||
|         out.write(privateEncryptionKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         Encode.varInt(pubkey.getVersion(), buffer); |  | ||||||
|         Encode.varInt(pubkey.getStream(), buffer); |  | ||||||
|         try { |  | ||||||
|             ByteArrayOutputStream baos = new ByteArrayOutputStream(); |  | ||||||
|             pubkey.writeUnencrypted(baos); |  | ||||||
|             Encode.varBytes(baos.toByteArray(), buffer); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|         Encode.varBytes(privateSigningKey, buffer); |  | ||||||
|         Encode.varBytes(privateEncryptionKey, buffer); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,100 +0,0 @@ | |||||||
| package ch.dissem.bitmessage.entity.valueobject.extended; |  | ||||||
|  |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 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. |  | ||||||
|  */ |  | ||||||
| public class Attachment implements Serializable { |  | ||||||
|     private static final long serialVersionUID = 7319139427666943189L; |  | ||||||
|  |  | ||||||
|     private String name; |  | ||||||
|     private byte[] data; |  | ||||||
|     private String type; |  | ||||||
|     private Disposition disposition; |  | ||||||
|  |  | ||||||
|     public String getName() { |  | ||||||
|         return name; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] getData() { |  | ||||||
|         return data; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getType() { |  | ||||||
|         return type; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Disposition getDisposition() { |  | ||||||
|         return disposition; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         Attachment that = (Attachment) o; |  | ||||||
|         return Objects.equals(name, that.name) && |  | ||||||
|             Arrays.equals(data, that.data) && |  | ||||||
|             Objects.equals(type, that.type) && |  | ||||||
|             disposition == that.disposition; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(name, data, type, disposition); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public enum Disposition { |  | ||||||
|         inline, attachment |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static final class Builder { |  | ||||||
|         private String name; |  | ||||||
|         private byte[] data; |  | ||||||
|         private String type; |  | ||||||
|         private Disposition disposition; |  | ||||||
|  |  | ||||||
|         public Builder name(String name) { |  | ||||||
|             this.name = name; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder data(byte[] data) { |  | ||||||
|             this.data = data; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder type(String type) { |  | ||||||
|             this.type = type; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder inline() { |  | ||||||
|             this.disposition = Disposition.inline; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder attachment() { |  | ||||||
|             this.disposition = Disposition.attachment; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder disposition(Disposition disposition) { |  | ||||||
|             this.disposition = disposition; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Attachment build() { |  | ||||||
|             Attachment attachment = new Attachment(); |  | ||||||
|             attachment.type = this.type; |  | ||||||
|             attachment.disposition = this.disposition; |  | ||||||
|             attachment.data = this.data; |  | ||||||
|             attachment.name = this.name; |  | ||||||
|             return attachment; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,219 +0,0 @@ | |||||||
| 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.msgpack.types.*; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| 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.*; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.valueobject.extended.Attachment.Disposition.attachment; |  | ||||||
| import static ch.dissem.bitmessage.utils.Strings.str; |  | ||||||
| import static ch.dissem.msgpack.types.Utils.mp; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work |  | ||||||
|  * properly with future PyBitmessage implementations. |  | ||||||
|  */ |  | ||||||
| public class Message implements ExtendedEncoding.ExtendedType { |  | ||||||
|     private static final long serialVersionUID = -2724977231484285467L; |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(Message.class); |  | ||||||
|  |  | ||||||
|     public static final String TYPE = "message"; |  | ||||||
|  |  | ||||||
|     private String subject; |  | ||||||
|     private String body; |  | ||||||
|     private List<InventoryVector> parents; |  | ||||||
|     private List<Attachment> files; |  | ||||||
|  |  | ||||||
|     private Message(Builder builder) { |  | ||||||
|         subject = builder.subject; |  | ||||||
|         body = builder.body; |  | ||||||
|         parents = Collections.unmodifiableList(builder.parents); |  | ||||||
|         files = Collections.unmodifiableList(builder.files); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getType() { |  | ||||||
|         return TYPE; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getSubject() { |  | ||||||
|         return subject; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getBody() { |  | ||||||
|         return body; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<InventoryVector> getParents() { |  | ||||||
|         return parents; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<Attachment> getFiles() { |  | ||||||
|         return files; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         Message message = (Message) o; |  | ||||||
|         return Objects.equals(subject, message.subject) && |  | ||||||
|             Objects.equals(body, message.body) && |  | ||||||
|             Objects.equals(parents, message.parents) && |  | ||||||
|             Objects.equals(files, message.files); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(subject, body, parents, files); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public MPMap<MPString, MPType<?>> pack() throws IOException { |  | ||||||
|         MPMap<MPString, MPType<?>> result = new MPMap<>(); |  | ||||||
|         result.put(mp(""), mp(TYPE)); |  | ||||||
|         result.put(mp("subject"), mp(subject)); |  | ||||||
|         result.put(mp("body"), mp(body)); |  | ||||||
|  |  | ||||||
|         if (!files.isEmpty()) { |  | ||||||
|             MPArray<MPMap<MPString, MPType<?>>> items = new MPArray<>(); |  | ||||||
|             result.put(mp("files"), items); |  | ||||||
|             for (Attachment file : files) { |  | ||||||
|                 MPMap<MPString, MPType<?>> item = new MPMap<>(); |  | ||||||
|                 item.put(mp("name"), mp(file.getName())); |  | ||||||
|                 item.put(mp("data"), mp(file.getData())); |  | ||||||
|                 item.put(mp("type"), mp(file.getType())); |  | ||||||
|                 item.put(mp("disposition"), mp(file.getDisposition().name())); |  | ||||||
|                 items.add(item); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (!parents.isEmpty()) { |  | ||||||
|             MPArray<MPBinary> items = new MPArray<>(); |  | ||||||
|             result.put(mp("parents"), items); |  | ||||||
|             for (InventoryVector parent : parents) { |  | ||||||
|                 items.add(mp(parent.getHash())); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Builder { |  | ||||||
|         private String subject; |  | ||||||
|         private String body; |  | ||||||
|         private List<InventoryVector> parents = new LinkedList<>(); |  | ||||||
|         private List<Attachment> files = new LinkedList<>(); |  | ||||||
|  |  | ||||||
|         public Builder subject(String subject) { |  | ||||||
|             this.subject = subject; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder body(String body) { |  | ||||||
|             this.body = body; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addParent(Plaintext parent) { |  | ||||||
|             if (parent != null) { |  | ||||||
|                 InventoryVector iv = parent.getInventoryVector(); |  | ||||||
|                 if (iv == null) { |  | ||||||
|                     LOG.debug("Ignored parent without IV"); |  | ||||||
|                 } else { |  | ||||||
|                     parents.add(iv); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addParent(InventoryVector iv) { |  | ||||||
|             if (iv != null) { |  | ||||||
|                 parents.add(iv); |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addFile(File file, Attachment.Disposition disposition) { |  | ||||||
|             if (file != null) { |  | ||||||
|                 try { |  | ||||||
|                     files.add(new Attachment.Builder() |  | ||||||
|                         .name(file.getName()) |  | ||||||
|                         .disposition(disposition) |  | ||||||
|                         .type(URLConnection.guessContentTypeFromStream(new FileInputStream(file))) |  | ||||||
|                         .data(Files.readAllBytes(file.toPath())) |  | ||||||
|                         .build()); |  | ||||||
|                 } catch (IOException e) { |  | ||||||
|                     LOG.error(e.getMessage(), e); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder addFile(Attachment file) { |  | ||||||
|             if (file != null) { |  | ||||||
|                 files.add(file); |  | ||||||
|             } |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public ExtendedEncoding build() { |  | ||||||
|             return new ExtendedEncoding(new Message(this)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements ExtendedEncoding.Unpacker<Message> { |  | ||||||
|         @Override |  | ||||||
|         public String getType() { |  | ||||||
|             return TYPE; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         @Override |  | ||||||
|         public Message unpack(MPMap<MPString, MPType<?>> map) { |  | ||||||
|             Message.Builder builder = new Message.Builder(); |  | ||||||
|             builder.subject(str(map.get(mp("subject")))); |  | ||||||
|             builder.body(str(map.get(mp("body")))); |  | ||||||
|             @SuppressWarnings("unchecked") |  | ||||||
|             MPArray<MPBinary> parents = (MPArray<MPBinary>) map.get(mp("parents")); |  | ||||||
|             if (parents != null) { |  | ||||||
|                 for (MPBinary parent : parents) { |  | ||||||
|                     builder.addParent(InventoryVector.fromHash(parent.getValue())); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             @SuppressWarnings("unchecked") |  | ||||||
|             MPArray<MPMap<MPString, MPType<?>>> files = (MPArray<MPMap<MPString, MPType<?>>>) map.get(mp("files")); |  | ||||||
|             if (files != null) { |  | ||||||
|                 for (MPMap<MPString, MPType<?>> item : files) { |  | ||||||
|                     Attachment.Builder b = new Attachment.Builder(); |  | ||||||
|                     b.name(str(item.get(mp("name")))); |  | ||||||
|                     b.data(bin(item.get(mp("data")))); |  | ||||||
|                     b.type(str(item.get(mp("type")))); |  | ||||||
|                     String disposition = str(item.get(mp("disposition"))); |  | ||||||
|                     if ("inline".equals(disposition)) { |  | ||||||
|                         b.inline(); |  | ||||||
|                     } else if ("attachment".equals(disposition)) { |  | ||||||
|                         b.attachment(); |  | ||||||
|                     } |  | ||||||
|                     builder.addFile(b.build()); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return new Message(builder); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private byte[] bin(MPType data) { |  | ||||||
|             if (data instanceof MPBinary) { |  | ||||||
|                 return ((MPBinary) data).getValue(); |  | ||||||
|             } else { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,114 +0,0 @@ | |||||||
| 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.msgpack.types.*; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Strings.str; |  | ||||||
| import static ch.dissem.msgpack.types.Utils.mp; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Extended encoding type 'vote'. Specification still outstanding, so this will need some work. |  | ||||||
|  */ |  | ||||||
| public class Vote implements ExtendedEncoding.ExtendedType { |  | ||||||
|     private static final long serialVersionUID = -8427038604209964837L; |  | ||||||
|  |  | ||||||
|     public static final String TYPE = "vote"; |  | ||||||
|  |  | ||||||
|     private InventoryVector msgId; |  | ||||||
|     private String vote; |  | ||||||
|  |  | ||||||
|     private Vote(Builder builder) { |  | ||||||
|         msgId = builder.msgId; |  | ||||||
|         vote = builder.vote; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String getType() { |  | ||||||
|         return TYPE; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public InventoryVector getMsgId() { |  | ||||||
|         return msgId; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getVote() { |  | ||||||
|         return vote; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) return true; |  | ||||||
|         if (o == null || getClass() != o.getClass()) return false; |  | ||||||
|         Vote vote1 = (Vote) o; |  | ||||||
|         return Objects.equals(msgId, vote1.msgId) && |  | ||||||
|             Objects.equals(vote, vote1.vote); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public int hashCode() { |  | ||||||
|         return Objects.hash(msgId, vote); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public MPMap<MPString, MPType<?>> pack() throws IOException { |  | ||||||
|         MPMap<MPString, MPType<?>> result = new MPMap<>(); |  | ||||||
|         result.put(mp(""), mp(TYPE)); |  | ||||||
|         result.put(mp("msgId"), mp(msgId.getHash())); |  | ||||||
|         result.put(mp("vote"), mp(vote)); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Builder { |  | ||||||
|         private InventoryVector msgId; |  | ||||||
|         private String vote; |  | ||||||
|  |  | ||||||
|         public ExtendedEncoding up(Plaintext message) { |  | ||||||
|             msgId = message.getInventoryVector(); |  | ||||||
|             vote = "1"; |  | ||||||
|             return new ExtendedEncoding(new Vote(this)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public ExtendedEncoding down(Plaintext message) { |  | ||||||
|             msgId = message.getInventoryVector(); |  | ||||||
|             vote = "1"; |  | ||||||
|             return new ExtendedEncoding(new Vote(this)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder msgId(InventoryVector iv) { |  | ||||||
|             this.msgId = iv; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Builder vote(String vote) { |  | ||||||
|             this.vote = vote; |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public ExtendedEncoding build() { |  | ||||||
|             return new ExtendedEncoding(new Vote(this)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static class Unpacker implements ExtendedEncoding.Unpacker<Vote> { |  | ||||||
|         @Override |  | ||||||
|         public String getType() { |  | ||||||
|             return TYPE; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         @Override |  | ||||||
|         public Vote unpack(MPMap<MPString, MPType<?>> map) { |  | ||||||
|             Vote.Builder builder = new Vote.Builder(); |  | ||||||
|             MPType<?> msgId = map.get(mp("msgId")); |  | ||||||
|             if (msgId instanceof MPBinary) { |  | ||||||
|                 builder.msgId(InventoryVector.fromHash(((MPBinary) msgId).getValue())); |  | ||||||
|             } |  | ||||||
|             builder.vote(str(map.get(mp("vote")))); |  | ||||||
|             return new Vote(builder); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.exception; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.utils.Strings; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.Arrays; |  | ||||||
|  |  | ||||||
| public class InsufficientProofOfWorkException extends IOException { |  | ||||||
|     private static final long serialVersionUID = 9105580366564571318L; |  | ||||||
|  |  | ||||||
|     public InsufficientProofOfWorkException(byte[] target, byte[] hash) { |  | ||||||
|         super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved."); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,92 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2016 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.factory; |  | ||||||
|  |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Stack; |  | ||||||
| import java.util.TreeMap; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.ports.NetworkHandler.HEADER_SIZE; |  | ||||||
| import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A pool for {@link ByteBuffer}s. As they may use up a lot of memory, |  | ||||||
|  * they should be reused as efficiently as possible. |  | ||||||
|  */ |  | ||||||
| class BufferPool { |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(BufferPool.class); |  | ||||||
|  |  | ||||||
|     public static final BufferPool bufferPool = new BufferPool(); |  | ||||||
|  |  | ||||||
|     private final Map<Integer, Stack<ByteBuffer>> pools = new TreeMap<>(); |  | ||||||
|  |  | ||||||
|     private BufferPool() { |  | ||||||
|         pools.put(HEADER_SIZE, new Stack<ByteBuffer>()); |  | ||||||
|         pools.put(54, new Stack<ByteBuffer>()); |  | ||||||
|         pools.put(1000, new Stack<ByteBuffer>()); |  | ||||||
|         pools.put(60000, new Stack<ByteBuffer>()); |  | ||||||
|         pools.put(MAX_PAYLOAD_SIZE, new Stack<ByteBuffer>()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public synchronized ByteBuffer allocate(int capacity) { |  | ||||||
|         Integer targetSize = getTargetSize(capacity); |  | ||||||
|         Stack<ByteBuffer> pool = pools.get(targetSize); |  | ||||||
|         if (pool.isEmpty()) { |  | ||||||
|             LOG.trace("Creating new buffer of size " + targetSize); |  | ||||||
|             return ByteBuffer.allocate(targetSize); |  | ||||||
|         } else { |  | ||||||
|             return pool.pop(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Returns a buffer that has the size of the Bitmessage network message header, 24 bytes. |  | ||||||
|      * |  | ||||||
|      * @return a buffer of size 24 |  | ||||||
|      */ |  | ||||||
|     public synchronized ByteBuffer allocateHeaderBuffer() { |  | ||||||
|         Stack<ByteBuffer> pool = pools.get(HEADER_SIZE); |  | ||||||
|         if (pool.isEmpty()) { |  | ||||||
|             return ByteBuffer.allocate(HEADER_SIZE); |  | ||||||
|         } else { |  | ||||||
|             return pool.pop(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public synchronized void deallocate(ByteBuffer buffer) { |  | ||||||
|         buffer.clear(); |  | ||||||
|         Stack<ByteBuffer> pool = pools.get(buffer.capacity()); |  | ||||||
|         if (pool == null) { |  | ||||||
|             throw new IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() + |  | ||||||
|                 " one of " + pools.keySet() + " expected."); |  | ||||||
|         } else { |  | ||||||
|             pool.push(buffer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private Integer getTargetSize(int capacity) { |  | ||||||
|         for (Integer size : pools.keySet()) { |  | ||||||
|             if (size >= capacity) return size; |  | ||||||
|         } |  | ||||||
|         throw new IllegalArgumentException("Requested capacity too large: " + |  | ||||||
|             "requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,62 +0,0 @@ | |||||||
| 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.msgpack.Reader; |  | ||||||
| import ch.dissem.msgpack.types.MPMap; |  | ||||||
| import ch.dissem.msgpack.types.MPString; |  | ||||||
| import ch.dissem.msgpack.types.MPType; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayInputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.HashMap; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.zip.InflaterInputStream; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Strings.str; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Factory that creates {@link ExtendedEncoding} objects from byte arrays. You can register your own types by adding a |  | ||||||
|  * {@link ExtendedEncoding.Unpacker} using {@link #registerFactory(ExtendedEncoding.Unpacker)}. |  | ||||||
|  */ |  | ||||||
| public class ExtendedEncodingFactory { |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class); |  | ||||||
|     private static final ExtendedEncodingFactory INSTANCE = new ExtendedEncodingFactory(); |  | ||||||
|     private static final MPString KEY_MESSAGE_TYPE = new MPString(""); |  | ||||||
|     private Map<String, ExtendedEncoding.Unpacker<?>> factories = new HashMap<>(); |  | ||||||
|  |  | ||||||
|     private ExtendedEncodingFactory() { |  | ||||||
|         registerFactory(new Message.Unpacker()); |  | ||||||
|         registerFactory(new Vote.Unpacker()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void registerFactory(ExtendedEncoding.Unpacker<?> factory) { |  | ||||||
|         factories.put(factory.getType(), factory); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public ExtendedEncoding unzip(byte[] zippedData) { |  | ||||||
|         try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) { |  | ||||||
|             Reader reader = Reader.getInstance(); |  | ||||||
|             @SuppressWarnings("unchecked") |  | ||||||
|             MPMap<MPString, MPType<?>> map = (MPMap<MPString, MPType<?>>) reader.read(unzipper); |  | ||||||
|             MPType<?> messageType = map.get(KEY_MESSAGE_TYPE); |  | ||||||
|             if (messageType == null) { |  | ||||||
|                 LOG.error("Missing message type"); |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|             ExtendedEncoding.Unpacker<?> factory = factories.get(str(messageType)); |  | ||||||
|             return new ExtendedEncoding(factory.unpack(map)); |  | ||||||
|         } catch (ClassCastException | IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static ExtendedEncodingFactory getInstance() { |  | ||||||
|         return INSTANCE; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,217 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.factory; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.NetworkMessage; |  | ||||||
| import ch.dissem.bitmessage.entity.ObjectMessage; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.*; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.PrivateKey; |  | ||||||
| import ch.dissem.bitmessage.exception.NodeException; |  | ||||||
| import ch.dissem.bitmessage.utils.TTL; |  | ||||||
| import ch.dissem.bitmessage.utils.UnixTime; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.net.SocketException; |  | ||||||
| import java.net.SocketTimeoutException; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Creates {@link NetworkMessage} objects from {@link InputStream InputStreams} |  | ||||||
|  */ |  | ||||||
| public class Factory { |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(Factory.class); |  | ||||||
|  |  | ||||||
|     public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException { |  | ||||||
|         try { |  | ||||||
|             return V3MessageFactory.read(stream); |  | ||||||
|         } catch (SocketTimeoutException | NodeException e) { |  | ||||||
|             throw e; |  | ||||||
|         } catch (SocketException e) { |  | ||||||
|             throw new NodeException(e.getMessage(), e); |  | ||||||
|         } catch (Exception e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static ObjectMessage getObjectMessage(int version, InputStream stream, int length) { |  | ||||||
|         try { |  | ||||||
|             return V3MessageFactory.readObject(stream, length); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             LOG.error(e.getMessage(), e); |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey, |  | ||||||
|                                       long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { |  | ||||||
|         return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes, |  | ||||||
|                 Pubkey.Feature.bitfield(features)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey, |  | ||||||
|                                       long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) { |  | ||||||
|         if (publicSigningKey.length != 64 && publicSigningKey.length != 65) |  | ||||||
|             throw new IllegalArgumentException("64 bytes signing key expected, but it was " |  | ||||||
|                     + publicSigningKey.length + " bytes long."); |  | ||||||
|         if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65) |  | ||||||
|             throw new IllegalArgumentException("64 bytes encryption key expected, but it was " |  | ||||||
|                     + publicEncryptionKey.length + " bytes long."); |  | ||||||
|  |  | ||||||
|         switch ((int) version) { |  | ||||||
|             case 2: |  | ||||||
|                 return new V2Pubkey.Builder() |  | ||||||
|                         .stream(stream) |  | ||||||
|                         .publicSigningKey(publicSigningKey) |  | ||||||
|                         .publicEncryptionKey(publicEncryptionKey) |  | ||||||
|                         .behaviorBitfield(behaviourBitfield) |  | ||||||
|                         .build(); |  | ||||||
|             case 3: |  | ||||||
|                 return new V3Pubkey.Builder() |  | ||||||
|                         .stream(stream) |  | ||||||
|                         .publicSigningKey(publicSigningKey) |  | ||||||
|                         .publicEncryptionKey(publicEncryptionKey) |  | ||||||
|                         .behaviorBitfield(behaviourBitfield) |  | ||||||
|                         .nonceTrialsPerByte(nonceTrialsPerByte) |  | ||||||
|                         .extraBytes(extraBytes) |  | ||||||
|                         .build(); |  | ||||||
|             case 4: |  | ||||||
|                 return new V4Pubkey( |  | ||||||
|                         new V3Pubkey.Builder() |  | ||||||
|                                 .stream(stream) |  | ||||||
|                                 .publicSigningKey(publicSigningKey) |  | ||||||
|                                 .publicEncryptionKey(publicEncryptionKey) |  | ||||||
|                                 .behaviorBitfield(behaviourBitfield) |  | ||||||
|                                 .nonceTrialsPerByte(nonceTrialsPerByte) |  | ||||||
|                                 .extraBytes(extraBytes) |  | ||||||
|                                 .build() |  | ||||||
|                 ); |  | ||||||
|             default: |  | ||||||
|                 throw new IllegalArgumentException("Unexpected pubkey version " + version); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static BitmessageAddress createIdentityFromPrivateKey(String address, |  | ||||||
|                                                                  byte[] privateSigningKey, byte[] privateEncryptionKey, |  | ||||||
|                                                                  long nonceTrialsPerByte, long extraBytes, |  | ||||||
|                                                                  int behaviourBitfield) { |  | ||||||
|         BitmessageAddress temp = new BitmessageAddress(address); |  | ||||||
|         PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey, |  | ||||||
|                 createPubkey(temp.getVersion(), temp.getStream(), |  | ||||||
|                         cryptography().createPublicKey(privateSigningKey), |  | ||||||
|                         cryptography().createPublicKey(privateEncryptionKey), |  | ||||||
|                         nonceTrialsPerByte, extraBytes, behaviourBitfield)); |  | ||||||
|         BitmessageAddress result = new BitmessageAddress(privateKey); |  | ||||||
|         if (!result.getAddress().equals(address)) { |  | ||||||
|             throw new IllegalArgumentException("Address not matching private key. Address: " + address |  | ||||||
|                     + "; Address derived from private key: " + result.getAddress()); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static BitmessageAddress generatePrivateAddress(boolean shorter, |  | ||||||
|                                                            long stream, |  | ||||||
|                                                            Pubkey.Feature... features) { |  | ||||||
|         return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     static ObjectPayload getObjectPayload(long objectType, |  | ||||||
|                                           long version, |  | ||||||
|                                           long streamNumber, |  | ||||||
|                                           InputStream stream, |  | ||||||
|                                           int length) throws IOException { |  | ||||||
|         ObjectType type = ObjectType.fromNumber(objectType); |  | ||||||
|         if (type != null) { |  | ||||||
|             switch (type) { |  | ||||||
|                 case GET_PUBKEY: |  | ||||||
|                     return parseGetPubkey(version, streamNumber, stream, length); |  | ||||||
|                 case PUBKEY: |  | ||||||
|                     return parsePubkey(version, streamNumber, stream, length); |  | ||||||
|                 case MSG: |  | ||||||
|                     return parseMsg(version, streamNumber, stream, length); |  | ||||||
|                 case BROADCAST: |  | ||||||
|                     return parseBroadcast(version, streamNumber, stream, length); |  | ||||||
|                 default: |  | ||||||
|                     LOG.error("This should not happen, someone broke something in the code!"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         // fallback: just store the message - we don't really care what it is |  | ||||||
|         LOG.trace("Unexpected object type: " + objectType); |  | ||||||
|         return GenericPayload.read(version, streamNumber, stream, length); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { |  | ||||||
|         return GetPubkey.read(stream, streamNumber, length, version); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException { |  | ||||||
|         switch ((int) version) { |  | ||||||
|             case 2: |  | ||||||
|                 return V2Pubkey.read(is, stream); |  | ||||||
|             case 3: |  | ||||||
|                 return V3Pubkey.read(is, stream); |  | ||||||
|             case 4: |  | ||||||
|                 return V4Pubkey.read(is, stream, length, encrypted); |  | ||||||
|         } |  | ||||||
|         LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object"); |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { |  | ||||||
|         Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true); |  | ||||||
|         return pubkey != null ? pubkey : GenericPayload.read(version, streamNumber, stream, length); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException { |  | ||||||
|         return Msg.read(stream, streamNumber, length); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException { |  | ||||||
|         switch ((int) version) { |  | ||||||
|             case 4: |  | ||||||
|                 return V4Broadcast.read(stream, streamNumber, length); |  | ||||||
|             case 5: |  | ||||||
|                 return V5Broadcast.read(stream, streamNumber, length); |  | ||||||
|             default: |  | ||||||
|                 LOG.debug("Encountered unknown broadcast version " + version); |  | ||||||
|                 return GenericPayload.read(version, streamNumber, stream, length); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static Broadcast getBroadcast(Plaintext plaintext) { |  | ||||||
|         BitmessageAddress sendingAddress = plaintext.getFrom(); |  | ||||||
|         if (sendingAddress.getVersion() < 4) { |  | ||||||
|             return new V4Broadcast(sendingAddress, plaintext); |  | ||||||
|         } else { |  | ||||||
|             return new V5Broadcast(sendingAddress, plaintext); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static ObjectMessage createAck(Plaintext plaintext) { |  | ||||||
|         if (plaintext == null || plaintext.getAckData() == null) |  | ||||||
|             return null; |  | ||||||
|         GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData()); |  | ||||||
|         return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(plaintext.getTTL())).build(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,236 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.factory; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.*; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.GenericPayload; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.ObjectPayload; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; |  | ||||||
| import ch.dissem.bitmessage.exception.NodeException; |  | ||||||
| import ch.dissem.bitmessage.utils.AccessCounter; |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
| import ch.dissem.bitmessage.utils.Strings; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayInputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Creates protocol v3 network messages from {@link InputStream InputStreams} |  | ||||||
|  */ |  | ||||||
| class V3MessageFactory { |  | ||||||
|     private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class); |  | ||||||
|  |  | ||||||
|     public static NetworkMessage read(InputStream in) throws IOException { |  | ||||||
|         findMagic(in); |  | ||||||
|         String command = getCommand(in); |  | ||||||
|         int length = (int) Decode.uint32(in); |  | ||||||
|         if (length > 1600003) { |  | ||||||
|             throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected."); |  | ||||||
|         } |  | ||||||
|         byte[] checksum = Decode.bytes(in, 4); |  | ||||||
|  |  | ||||||
|         byte[] payloadBytes = Decode.bytes(in, length); |  | ||||||
|  |  | ||||||
|         if (testChecksum(checksum, payloadBytes)) { |  | ||||||
|             MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length); |  | ||||||
|             if (payload != null) |  | ||||||
|                 return new NetworkMessage(payload); |  | ||||||
|             else |  | ||||||
|                 return null; |  | ||||||
|         } else { |  | ||||||
|             throw new IOException("Checksum failed for message '" + command + "'"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException { |  | ||||||
|         switch (command) { |  | ||||||
|             case "version": |  | ||||||
|                 return parseVersion(stream); |  | ||||||
|             case "verack": |  | ||||||
|                 return new VerAck(); |  | ||||||
|             case "addr": |  | ||||||
|                 return parseAddr(stream); |  | ||||||
|             case "inv": |  | ||||||
|                 return parseInv(stream); |  | ||||||
|             case "getdata": |  | ||||||
|                 return parseGetData(stream); |  | ||||||
|             case "object": |  | ||||||
|                 return readObject(stream, length); |  | ||||||
|             case "custom": |  | ||||||
|                 return readCustom(stream, length); |  | ||||||
|             default: |  | ||||||
|                 LOG.debug("Unknown command: " + command); |  | ||||||
|                 return null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static MessagePayload readCustom(InputStream in, int length) throws IOException { |  | ||||||
|         return CustomMessage.read(in, length); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static ObjectMessage readObject(InputStream in, int length) throws IOException { |  | ||||||
|         AccessCounter counter = new AccessCounter(); |  | ||||||
|         byte nonce[] = Decode.bytes(in, 8, counter); |  | ||||||
|         long expiresTime = Decode.int64(in, counter); |  | ||||||
|         long objectType = Decode.uint32(in, counter); |  | ||||||
|         long version = Decode.varInt(in, counter); |  | ||||||
|         long stream = Decode.varInt(in, counter); |  | ||||||
|  |  | ||||||
|         byte[] data = Decode.bytes(in, length - counter.length()); |  | ||||||
|         ObjectPayload payload; |  | ||||||
|         try { |  | ||||||
|             ByteArrayInputStream dataStream = new ByteArrayInputStream(data); |  | ||||||
|             payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length); |  | ||||||
|         } catch (Exception e) { |  | ||||||
|             if (LOG.isTraceEnabled()) { |  | ||||||
|                 LOG.trace("Could not parse object payload - using generic payload instead", e); |  | ||||||
|                 LOG.trace(Strings.hex(data).toString()); |  | ||||||
|             } |  | ||||||
|             payload = new GenericPayload(version, stream, data); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return new ObjectMessage.Builder() |  | ||||||
|             .nonce(nonce) |  | ||||||
|             .expiresTime(expiresTime) |  | ||||||
|             .objectType(objectType) |  | ||||||
|             .stream(stream) |  | ||||||
|             .payload(payload) |  | ||||||
|             .build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static GetData parseGetData(InputStream stream) throws IOException { |  | ||||||
|         long count = Decode.varInt(stream); |  | ||||||
|         GetData.Builder builder = new GetData.Builder(); |  | ||||||
|         for (int i = 0; i < count; i++) { |  | ||||||
|             builder.addInventoryVector(parseInventoryVector(stream)); |  | ||||||
|         } |  | ||||||
|         return builder.build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static Inv parseInv(InputStream stream) throws IOException { |  | ||||||
|         long count = Decode.varInt(stream); |  | ||||||
|         Inv.Builder builder = new Inv.Builder(); |  | ||||||
|         for (int i = 0; i < count; i++) { |  | ||||||
|             builder.addInventoryVector(parseInventoryVector(stream)); |  | ||||||
|         } |  | ||||||
|         return builder.build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static Addr parseAddr(InputStream stream) throws IOException { |  | ||||||
|         long count = Decode.varInt(stream); |  | ||||||
|         Addr.Builder builder = new Addr.Builder(); |  | ||||||
|         for (int i = 0; i < count; i++) { |  | ||||||
|             builder.addAddress(parseAddress(stream, false)); |  | ||||||
|         } |  | ||||||
|         return builder.build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static Version parseVersion(InputStream stream) throws IOException { |  | ||||||
|         int version = Decode.int32(stream); |  | ||||||
|         long services = Decode.int64(stream); |  | ||||||
|         long timestamp = Decode.int64(stream); |  | ||||||
|         NetworkAddress addrRecv = parseAddress(stream, true); |  | ||||||
|         NetworkAddress addrFrom = parseAddress(stream, true); |  | ||||||
|         long nonce = Decode.int64(stream); |  | ||||||
|         String userAgent = Decode.varString(stream); |  | ||||||
|         long[] streamNumbers = Decode.varIntList(stream); |  | ||||||
|  |  | ||||||
|         return new Version.Builder() |  | ||||||
|             .version(version) |  | ||||||
|             .services(services) |  | ||||||
|             .timestamp(timestamp) |  | ||||||
|             .addrRecv(addrRecv).addrFrom(addrFrom) |  | ||||||
|             .nonce(nonce) |  | ||||||
|             .userAgent(userAgent) |  | ||||||
|             .streams(streamNumbers).build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static InventoryVector parseInventoryVector(InputStream stream) throws IOException { |  | ||||||
|         return InventoryVector.fromHash(Decode.bytes(stream, 32)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException { |  | ||||||
|         long time; |  | ||||||
|         long streamNumber; |  | ||||||
|         if (!light) { |  | ||||||
|             time = Decode.int64(stream); |  | ||||||
|             streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct |  | ||||||
|         } else { |  | ||||||
|             time = 0; |  | ||||||
|             streamNumber = 0; |  | ||||||
|         } |  | ||||||
|         long services = Decode.int64(stream); |  | ||||||
|         byte[] ipv6 = Decode.bytes(stream, 16); |  | ||||||
|         int port = Decode.uint16(stream); |  | ||||||
|         return new NetworkAddress.Builder() |  | ||||||
|             .time(time) |  | ||||||
|             .stream(streamNumber) |  | ||||||
|             .services(services) |  | ||||||
|             .ipv6(ipv6) |  | ||||||
|             .port(port) |  | ||||||
|             .build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static boolean testChecksum(byte[] checksum, byte[] payload) { |  | ||||||
|         byte[] payloadChecksum = cryptography().sha512(payload); |  | ||||||
|         for (int i = 0; i < checksum.length; i++) { |  | ||||||
|             if (checksum[i] != payloadChecksum[i]) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static String getCommand(InputStream stream) throws IOException { |  | ||||||
|         byte[] bytes = new byte[12]; |  | ||||||
|         int end = bytes.length; |  | ||||||
|         for (int i = 0; i < bytes.length; i++) { |  | ||||||
|             bytes[i] = (byte) stream.read(); |  | ||||||
|             if (end == bytes.length) { |  | ||||||
|                 if (bytes[i] == 0) end = i; |  | ||||||
|             } else { |  | ||||||
|                 if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return new String(bytes, 0, end, "ASCII"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static void findMagic(InputStream in) throws IOException { |  | ||||||
|         int pos = 0; |  | ||||||
|         for (int i = 0; i < 1620000; i++) { |  | ||||||
|             byte b = (byte) in.read(); |  | ||||||
|             if (b == MAGIC_BYTES[pos]) { |  | ||||||
|                 if (pos + 1 == MAGIC_BYTES.length) { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|             } else if (pos > 0 && b == MAGIC_BYTES[0]) { |  | ||||||
|                 pos = 1; |  | ||||||
|             } else { |  | ||||||
|                 pos = 0; |  | ||||||
|             } |  | ||||||
|             pos++; |  | ||||||
|         } |  | ||||||
|         throw new NodeException("Failed to find MAGIC bytes in stream"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,189 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2016 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.factory; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.MessagePayload; |  | ||||||
| import ch.dissem.bitmessage.entity.NetworkMessage; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.exception.NodeException; |  | ||||||
| import ch.dissem.bitmessage.utils.Decode; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayInputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.UnsupportedEncodingException; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| import java.util.LinkedList; |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES; |  | ||||||
| import static ch.dissem.bitmessage.factory.BufferPool.bufferPool; |  | ||||||
| import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE; |  | ||||||
| import static ch.dissem.bitmessage.utils.Singleton.cryptography; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Similar to the {@link V3MessageFactory}, but used for NIO buffers which may or may not contain a whole message. |  | ||||||
|  */ |  | ||||||
| public class V3MessageReader { |  | ||||||
|     private ByteBuffer headerBuffer; |  | ||||||
|     private ByteBuffer dataBuffer; |  | ||||||
|  |  | ||||||
|     private ReaderState state = ReaderState.MAGIC; |  | ||||||
|     private String command; |  | ||||||
|     private int length; |  | ||||||
|     private byte[] checksum; |  | ||||||
|  |  | ||||||
|     private List<NetworkMessage> messages = new LinkedList<>(); |  | ||||||
|  |  | ||||||
|     public ByteBuffer getActiveBuffer() { |  | ||||||
|         if (state != null && state != ReaderState.DATA) { |  | ||||||
|             if (headerBuffer == null) { |  | ||||||
|                 headerBuffer = bufferPool.allocateHeaderBuffer(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return state == ReaderState.DATA ? dataBuffer : headerBuffer; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void update() { |  | ||||||
|         if (state != ReaderState.DATA) { |  | ||||||
|             getActiveBuffer(); |  | ||||||
|             headerBuffer.flip(); |  | ||||||
|         } |  | ||||||
|         switch (state) { |  | ||||||
|             case MAGIC: |  | ||||||
|                 if (!findMagicBytes(headerBuffer)) { |  | ||||||
|                     headerBuffer.compact(); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 state = ReaderState.HEADER; |  | ||||||
|             case HEADER: |  | ||||||
|                 if (headerBuffer.remaining() < 20) { |  | ||||||
|                     headerBuffer.compact(); |  | ||||||
|                     headerBuffer.limit(20); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 command = getCommand(headerBuffer); |  | ||||||
|                 length = (int) Decode.uint32(headerBuffer); |  | ||||||
|                 if (length > MAX_PAYLOAD_SIZE) { |  | ||||||
|                     throw new NodeException("Payload of " + length + " bytes received, no more than " + |  | ||||||
|                         MAX_PAYLOAD_SIZE + " was expected."); |  | ||||||
|                 } |  | ||||||
|                 checksum = new byte[4]; |  | ||||||
|                 headerBuffer.get(checksum); |  | ||||||
|                 state = ReaderState.DATA; |  | ||||||
|                 bufferPool.deallocate(headerBuffer); |  | ||||||
|                 headerBuffer = null; |  | ||||||
|                 dataBuffer = bufferPool.allocate(length); |  | ||||||
|                 dataBuffer.clear(); |  | ||||||
|                 dataBuffer.limit(length); |  | ||||||
|             case DATA: |  | ||||||
|                 if (dataBuffer.position() < length) { |  | ||||||
|                     return; |  | ||||||
|                 } else { |  | ||||||
|                     dataBuffer.flip(); |  | ||||||
|                 } |  | ||||||
|                 if (!testChecksum(dataBuffer)) { |  | ||||||
|                     state = ReaderState.MAGIC; |  | ||||||
|                     throw new NodeException("Checksum failed for message '" + command + "'"); |  | ||||||
|                 } |  | ||||||
|                 try { |  | ||||||
|                     MessagePayload payload = V3MessageFactory.getPayload( |  | ||||||
|                         command, |  | ||||||
|                         new ByteArrayInputStream(dataBuffer.array(), |  | ||||||
|                             dataBuffer.arrayOffset() + dataBuffer.position(), length), |  | ||||||
|                         length); |  | ||||||
|                     if (payload != null) { |  | ||||||
|                         messages.add(new NetworkMessage(payload)); |  | ||||||
|                     } |  | ||||||
|                 } catch (IOException e) { |  | ||||||
|                     throw new NodeException(e.getMessage()); |  | ||||||
|                 } finally { |  | ||||||
|                     state = ReaderState.MAGIC; |  | ||||||
|                     bufferPool.deallocate(dataBuffer); |  | ||||||
|                     dataBuffer = null; |  | ||||||
|                     dataBuffer = null; |  | ||||||
|                 } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<NetworkMessage> getMessages() { |  | ||||||
|         return messages; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private boolean findMagicBytes(ByteBuffer buffer) { |  | ||||||
|         int i = 0; |  | ||||||
|         while (buffer.hasRemaining()) { |  | ||||||
|             if (i == 0) { |  | ||||||
|                 buffer.mark(); |  | ||||||
|             } |  | ||||||
|             if (buffer.get() == MAGIC_BYTES[i]) { |  | ||||||
|                 i++; |  | ||||||
|                 if (i == MAGIC_BYTES.length) { |  | ||||||
|                     return true; |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 i = 0; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (i > 0) { |  | ||||||
|             buffer.reset(); |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static String getCommand(ByteBuffer buffer) { |  | ||||||
|         int start = buffer.position(); |  | ||||||
|         int l = 0; |  | ||||||
|         while (l < 12 && buffer.get() != 0) l++; |  | ||||||
|         int i = l + 1; |  | ||||||
|         while (i < 12) { |  | ||||||
|             if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command"); |  | ||||||
|             i++; |  | ||||||
|         } |  | ||||||
|         try { |  | ||||||
|             return new String(buffer.array(), start, l, "ASCII"); |  | ||||||
|         } catch (UnsupportedEncodingException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private boolean testChecksum(ByteBuffer buffer) { |  | ||||||
|         byte[] payloadChecksum = cryptography().sha512(buffer.array(), |  | ||||||
|             buffer.arrayOffset() + buffer.position(), length); |  | ||||||
|         for (int i = 0; i < checksum.length; i++) { |  | ||||||
|             if (checksum[i] != payloadChecksum[i]) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its |  | ||||||
|      * connection is severed. |  | ||||||
|      */ |  | ||||||
|     public void cleanup() { |  | ||||||
|         state = null; |  | ||||||
|         if (headerBuffer != null) { |  | ||||||
|             bufferPool.deallocate(headerBuffer); |  | ||||||
|         } |  | ||||||
|         if (dataBuffer != null) { |  | ||||||
|             bufferPool.deallocate(dataBuffer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private enum ReaderState {MAGIC, HEADER, DATA} |  | ||||||
| } |  | ||||||
| @@ -1,214 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.ports; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.InternalContext; |  | ||||||
| import ch.dissem.bitmessage.entity.ObjectMessage; |  | ||||||
| import ch.dissem.bitmessage.entity.payload.Pubkey; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; |  | ||||||
| import ch.dissem.bitmessage.factory.Factory; |  | ||||||
| import ch.dissem.bitmessage.utils.Bytes; |  | ||||||
| import ch.dissem.bitmessage.utils.UnixTime; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import javax.crypto.Mac; |  | ||||||
| import javax.crypto.spec.SecretKeySpec; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.math.BigInteger; |  | ||||||
| import java.security.*; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; |  | ||||||
| import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; |  | ||||||
| import static ch.dissem.bitmessage.utils.Numbers.max; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. |  | ||||||
|  */ |  | ||||||
| public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder { |  | ||||||
|     protected static final Logger LOG = LoggerFactory.getLogger(Cryptography.class); |  | ||||||
|     private static final SecureRandom RANDOM = new SecureRandom(); |  | ||||||
|     private static final BigInteger TWO = BigInteger.valueOf(2); |  | ||||||
|     private static final BigInteger TWO_POW_64 = TWO.pow(64); |  | ||||||
|     private static final BigInteger TWO_POW_16 = TWO.pow(16); |  | ||||||
|  |  | ||||||
|     protected static final String ALGORITHM_ECDSA = "ECDSA"; |  | ||||||
|     protected static final String ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"; |  | ||||||
|     protected static final String ALGORITHM_EVP_SHA256 = "SHA256withECDSA"; |  | ||||||
|  |  | ||||||
|     protected final Provider provider; |  | ||||||
|     private InternalContext context; |  | ||||||
|  |  | ||||||
|     protected AbstractCryptography(Provider provider) { |  | ||||||
|         this.provider = provider; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setContext(InternalContext context) { |  | ||||||
|         this.context = context; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] sha512(byte[] data, int offset, int length) { |  | ||||||
|         MessageDigest mda = md("SHA-512"); |  | ||||||
|         mda.update(data, offset, length); |  | ||||||
|         return mda.digest(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] sha512(byte[]... data) { |  | ||||||
|         return hash("SHA-512", data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] doubleSha512(byte[]... data) { |  | ||||||
|         MessageDigest mda = md("SHA-512"); |  | ||||||
|         for (byte[] d : data) { |  | ||||||
|             mda.update(d); |  | ||||||
|         } |  | ||||||
|         return mda.digest(mda.digest()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] doubleSha512(byte[] data, int length) { |  | ||||||
|         MessageDigest mda = md("SHA-512"); |  | ||||||
|         mda.update(data, 0, length); |  | ||||||
|         return mda.digest(mda.digest()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] ripemd160(byte[]... data) { |  | ||||||
|         return hash("RIPEMD160", data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] doubleSha256(byte[] data, int length) { |  | ||||||
|         MessageDigest mda = md("SHA-256"); |  | ||||||
|         mda.update(data, 0, length); |  | ||||||
|         return mda.digest(mda.digest()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] sha1(byte[]... data) { |  | ||||||
|         return hash("SHA-1", data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] randomBytes(int length) { |  | ||||||
|         byte[] result = new byte[length]; |  | ||||||
|         RANDOM.nextBytes(result); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, |  | ||||||
|                               long extraBytes, ProofOfWorkEngine.Callback callback) { |  | ||||||
|         nonceTrialsPerByte = max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE); |  | ||||||
|         extraBytes = max(extraBytes, NETWORK_EXTRA_BYTES); |  | ||||||
|  |  | ||||||
|         byte[] initialHash = getInitialHash(object); |  | ||||||
|  |  | ||||||
|         byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); |  | ||||||
|  |  | ||||||
|         context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) |  | ||||||
|             throws IOException { |  | ||||||
|         byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); |  | ||||||
|         byte[] value = doubleSha512(object.getNonce(), getInitialHash(object)); |  | ||||||
|         if (Bytes.lt(target, value, 8)) { |  | ||||||
|             throw new InsufficientProofOfWorkException(target, value); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected byte[] doSign(byte[] data, java.security.PrivateKey privKey) throws GeneralSecurityException { |  | ||||||
|         // TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network |  | ||||||
|         Signature sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider); |  | ||||||
|         sig.initSign(privKey); |  | ||||||
|         sig.update(data); |  | ||||||
|         return sig.sign(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     protected boolean doCheckSignature(byte[] data, byte[] signature, PublicKey publicKey) throws GeneralSecurityException { |  | ||||||
|         for (String algorithm : new String[]{ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256}) { |  | ||||||
|             Signature sig = Signature.getInstance(algorithm, provider); |  | ||||||
|             sig.initVerify(publicKey); |  | ||||||
|             sig.update(data); |  | ||||||
|             if (sig.verify(signature)) { |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getInitialHash(ObjectMessage object) { |  | ||||||
|         return sha512(object.getPayloadBytesWithoutNonce()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { |  | ||||||
|         if (nonceTrialsPerByte == 0) nonceTrialsPerByte = NETWORK_NONCE_TRIALS_PER_BYTE; |  | ||||||
|         if (extraBytes == 0) extraBytes = NETWORK_EXTRA_BYTES; |  | ||||||
|  |  | ||||||
|         BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now()); |  | ||||||
|         BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes); |  | ||||||
|         BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte) |  | ||||||
|                 .multiply( |  | ||||||
|                         powLength.add( |  | ||||||
|                                 powLength.multiply(TTL).divide(TWO_POW_16) |  | ||||||
|                         ) |  | ||||||
|                 ); |  | ||||||
|         return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private byte[] hash(String algorithm, byte[]... data) { |  | ||||||
|         MessageDigest mda = md(algorithm); |  | ||||||
|         for (byte[] d : data) { |  | ||||||
|             mda.update(d); |  | ||||||
|         } |  | ||||||
|         return mda.digest(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private MessageDigest md(String algorithm) { |  | ||||||
|         try { |  | ||||||
|             return MessageDigest.getInstance(algorithm, provider); |  | ||||||
|         } catch (GeneralSecurityException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public byte[] mac(byte[] key_m, byte[] data) { |  | ||||||
|         try { |  | ||||||
|             Mac mac = Mac.getInstance("HmacSHA256", provider); |  | ||||||
|             mac.init(new SecretKeySpec(key_m, "HmacSHA256")); |  | ||||||
|             return mac.doFinal(data); |  | ||||||
|         } catch (GeneralSecurityException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey, |  | ||||||
|                                long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { |  | ||||||
|         return Factory.createPubkey(version, stream, |  | ||||||
|                 createPublicKey(privateSigningKey), |  | ||||||
|                 createPublicKey(privateEncryptionKey), |  | ||||||
|                 nonceTrialsPerByte, extraBytes, features); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public BigInteger keyToBigInt(byte[] privateKey) { |  | ||||||
|         return new BigInteger(1, privateKey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public long randomNonce() { |  | ||||||
|         return RANDOM.nextLong(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,162 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2016 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.ports; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.InternalContext; |  | ||||||
| import ch.dissem.bitmessage.entity.BitmessageAddress; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.Label; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.Strings; |  | ||||||
| import ch.dissem.bitmessage.utils.UnixTime; |  | ||||||
|  |  | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.UUID; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.SqlStrings.join; |  | ||||||
|  |  | ||||||
| public abstract class AbstractMessageRepository implements MessageRepository, InternalContext.ContextHolder { |  | ||||||
|     protected InternalContext ctx; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setContext(InternalContext context) { |  | ||||||
|         this.ctx = context; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @deprecated use {@link #saveContactIfNecessary(BitmessageAddress)} instead. |  | ||||||
|      */ |  | ||||||
|     @Deprecated |  | ||||||
|     protected void safeSenderIfNecessary(Plaintext message) { |  | ||||||
|         if (message.getId() == null) { |  | ||||||
|             saveContactIfNecessary(message.getFrom()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void saveContactIfNecessary(BitmessageAddress contact) { |  | ||||||
|         if (contact != null) { |  | ||||||
|             BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(contact.getAddress()); |  | ||||||
|             if (savedAddress == null) { |  | ||||||
|                 ctx.getAddressRepository().save(contact); |  | ||||||
|             } else if (savedAddress.getPubkey() == null && contact.getPubkey() != null) { |  | ||||||
|                 savedAddress.setPubkey(contact.getPubkey()); |  | ||||||
|                 ctx.getAddressRepository().save(savedAddress); |  | ||||||
|             } |  | ||||||
|             if (savedAddress != null) { |  | ||||||
|                 contact.setAlias(savedAddress.getAlias()); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Plaintext getMessage(Object id) { |  | ||||||
|         if (id instanceof Long) { |  | ||||||
|             return single(find("id=" + id)); |  | ||||||
|         } else { |  | ||||||
|             throw new IllegalArgumentException("Long expected for ID"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Plaintext getMessage(InventoryVector iv) { |  | ||||||
|         return single(find("iv=X'" + Strings.hex(iv.getHash()) + "'")); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Plaintext getMessage(byte[] initialHash) { |  | ||||||
|         return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'")); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public Plaintext getMessageForAck(byte[] ackData) { |  | ||||||
|         return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'")); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Plaintext> findMessages(Label label) { |  | ||||||
|         if (label == null) { |  | ||||||
|             return find("id NOT IN (SELECT message_id FROM Message_Label)"); |  | ||||||
|         } else { |  | ||||||
|             return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { |  | ||||||
|         return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Plaintext> findMessages(Plaintext.Status status) { |  | ||||||
|         return find("status='" + status.name() + "'"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Plaintext> findMessages(BitmessageAddress sender) { |  | ||||||
|         return find("sender='" + sender.getAddress() + "'"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Plaintext> findMessagesToResend() { |  | ||||||
|         return find("status='" + Plaintext.Status.SENT.name() + "'" + |  | ||||||
|             " AND next_try < " + UnixTime.now()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Plaintext> findResponses(Plaintext parent) { |  | ||||||
|         if (parent.getInventoryVector() == null) { |  | ||||||
|             return Collections.emptyList(); |  | ||||||
|         } |  | ||||||
|         return find("iv IN (SELECT child FROM Message_Parent" |  | ||||||
|             + " WHERE parent=X'" + Strings.hex(parent.getInventoryVector().getHash()) + "')"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Plaintext> getConversation(UUID conversationId) { |  | ||||||
|         return find("conversation=X'" + conversationId.toString().replace("-", "") + "'"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Label> getLabels() { |  | ||||||
|         return findLabels("1=1"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<Label> getLabels(Label.Type... types) { |  | ||||||
|         return findLabels("type IN (" + join(types) + ")"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected abstract List<Label> findLabels(String where); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     protected <T> T single(Collection<T> collection) { |  | ||||||
|         switch (collection.size()) { |  | ||||||
|             case 0: |  | ||||||
|                 return null; |  | ||||||
|             case 1: |  | ||||||
|                 return collection.iterator().next(); |  | ||||||
|             default: |  | ||||||
|                 throw new ApplicationException("This shouldn't happen, found " + collection.size() + |  | ||||||
|                     " items, one or none was expected"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected abstract List<Plaintext> find(String where); |  | ||||||
| } |  | ||||||
| @@ -1,92 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2016 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.ports; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.InternalContext; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.Label; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.entity.Plaintext.Status.*; |  | ||||||
|  |  | ||||||
| public class DefaultLabeler implements Labeler, InternalContext.ContextHolder { |  | ||||||
|     private InternalContext ctx; |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setLabels(Plaintext msg) { |  | ||||||
|         msg.setStatus(RECEIVED); |  | ||||||
|         if (msg.getType() == Plaintext.Type.BROADCAST) { |  | ||||||
|             msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD)); |  | ||||||
|         } else { |  | ||||||
|             msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void markAsDraft(Plaintext msg) { |  | ||||||
|         msg.setStatus(DRAFT); |  | ||||||
|         msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.DRAFT)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void markAsSending(Plaintext msg) { |  | ||||||
|         if (msg.getTo() != null && msg.getTo().getPubkey() == null) { |  | ||||||
|             msg.setStatus(PUBKEY_REQUESTED); |  | ||||||
|         } else { |  | ||||||
|             msg.setStatus(DOING_PROOF_OF_WORK); |  | ||||||
|         } |  | ||||||
|         msg.removeLabel(Label.Type.DRAFT); |  | ||||||
|         msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void markAsSent(Plaintext msg) { |  | ||||||
|         msg.setStatus(SENT); |  | ||||||
|         msg.removeLabel(Label.Type.OUTBOX); |  | ||||||
|         msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void markAsAcknowledged(Plaintext msg) { |  | ||||||
|         msg.setStatus(SENT_ACKNOWLEDGED); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void markAsRead(Plaintext msg) { |  | ||||||
|         msg.removeLabel(Label.Type.UNREAD); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void markAsUnread(Plaintext msg) { |  | ||||||
|         msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.UNREAD)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void delete(Plaintext msg) { |  | ||||||
|         msg.getLabels().clear(); |  | ||||||
|         msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.TRASH)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void archive(Plaintext msg) { |  | ||||||
|         msg.getLabels().clear(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void setContext(InternalContext ctx) { |  | ||||||
|         this.ctx = ctx; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,75 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.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.Collection; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.UUID; |  | ||||||
|  |  | ||||||
| public interface MessageRepository { |  | ||||||
|     List<Label> getLabels(); |  | ||||||
|  |  | ||||||
|     List<Label> getLabels(Label.Type... types); |  | ||||||
|  |  | ||||||
|     int countUnread(Label label); |  | ||||||
|  |  | ||||||
|     Plaintext getMessage(Object id); |  | ||||||
|  |  | ||||||
|     Plaintext getMessage(InventoryVector iv); |  | ||||||
|  |  | ||||||
|     Plaintext getMessage(byte[] initialHash); |  | ||||||
|  |  | ||||||
|     Plaintext getMessageForAck(byte[] ackData); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @param label to search for |  | ||||||
|      * @return a distinct list of all conversations that have at least one message with the given label. |  | ||||||
|      */ |  | ||||||
|     List<UUID> findConversations(Label label); |  | ||||||
|  |  | ||||||
|     List<Plaintext> findMessages(Label label); |  | ||||||
|  |  | ||||||
|     List<Plaintext> findMessages(Status status); |  | ||||||
|  |  | ||||||
|     List<Plaintext> findMessages(Status status, BitmessageAddress recipient); |  | ||||||
|  |  | ||||||
|     List<Plaintext> findMessages(BitmessageAddress sender); |  | ||||||
|  |  | ||||||
|     List<Plaintext> findResponses(Plaintext parent); |  | ||||||
|  |  | ||||||
|     List<Plaintext> findMessagesToResend(); |  | ||||||
|  |  | ||||||
|     void save(Plaintext message); |  | ||||||
|  |  | ||||||
|     void remove(Plaintext message); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Returns all messages with this conversation ID. The returned messages aren't sorted in any way, |  | ||||||
|      * so you may prefer to use {@link ch.dissem.bitmessage.utils.ConversationService#getConversation(UUID)} |  | ||||||
|      * instead. |  | ||||||
|      * |  | ||||||
|      * @param conversationId ID of the requested conversation |  | ||||||
|      * @return all messages with the given conversation ID |  | ||||||
|      */ |  | ||||||
|     Collection<Plaintext> getConversation(UUID conversationId); |  | ||||||
| } |  | ||||||
| @@ -1,128 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.ports; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.Bytes; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.security.MessageDigest; |  | ||||||
| import java.security.NoSuchAlgorithmException; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.concurrent.*; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Bytes.inc; |  | ||||||
| import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A POW engine using all available CPU cores. |  | ||||||
|  */ |  | ||||||
| public class MultiThreadedPOWEngine implements ProofOfWorkEngine { |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class); |  | ||||||
|     private final ExecutorService waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build()); |  | ||||||
|     private final ExecutorService workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build()); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * This method will block until all pending nonce calculations are done, but not wait for its own calculation |  | ||||||
|      * to finish. |  | ||||||
|      * (This implementation becomes very inefficient if multiple nonce are calculated at the same time.) |  | ||||||
|      * |  | ||||||
|      * @param initialHash the SHA-512 hash of the object to send, sans nonce |  | ||||||
|      * @param target      the target, representing an unsigned long |  | ||||||
|      * @param callback    called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make |  | ||||||
|      */ |  | ||||||
|     @Override |  | ||||||
|     public void calculateNonce(final byte[] initialHash, final byte[] target, final Callback callback) { |  | ||||||
|         waiterPool.execute(new Runnable() { |  | ||||||
|             @Override |  | ||||||
|             public void run() { |  | ||||||
|                 long startTime = System.currentTimeMillis(); |  | ||||||
|  |  | ||||||
|                 int cores = Runtime.getRuntime().availableProcessors(); |  | ||||||
|                 if (cores > 255) cores = 255; |  | ||||||
|                 LOG.info("Doing POW using " + cores + " cores"); |  | ||||||
|                 List<Worker> workers = new ArrayList<>(cores); |  | ||||||
|                 for (int i = 0; i < cores; i++) { |  | ||||||
|                     Worker w = new Worker((byte) cores, i, initialHash, target); |  | ||||||
|                     workers.add(w); |  | ||||||
|                 } |  | ||||||
|                 List<Future<byte[]>> futures = new ArrayList<>(cores); |  | ||||||
|                 for (Worker w : workers) { |  | ||||||
|                     // Doing this in the previous loop might cause a ConcurrentModificationException in the worker |  | ||||||
|                     // if a worker finds a nonce while new ones are still being added. |  | ||||||
|                     futures.add(workerPool.submit(w)); |  | ||||||
|                 } |  | ||||||
|                 try { |  | ||||||
|                     while (!Thread.interrupted()) { |  | ||||||
|                         for (Future<byte[]> future : futures) { |  | ||||||
|                             if (future.isDone()) { |  | ||||||
|                                 callback.onNonceCalculated(initialHash, future.get()); |  | ||||||
|                                 LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds"); |  | ||||||
|                                 for (Future<byte[]> f : futures) { |  | ||||||
|                                     f.cancel(true); |  | ||||||
|                                 } |  | ||||||
|                                 return; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         Thread.sleep(100); |  | ||||||
|                     } |  | ||||||
|                     LOG.error("POW waiter thread interrupted - this should not happen!"); |  | ||||||
|                 } catch (ExecutionException e) { |  | ||||||
|                     LOG.error(e.getMessage(), e); |  | ||||||
|                 } catch (InterruptedException e) { |  | ||||||
|                     LOG.error("POW waiter thread interrupted - this should not happen!", e); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private class Worker implements Callable<byte[]> { |  | ||||||
|         private final byte numberOfCores; |  | ||||||
|         private final byte[] initialHash; |  | ||||||
|         private final byte[] target; |  | ||||||
|         private final MessageDigest mda; |  | ||||||
|         private final byte[] nonce = new byte[8]; |  | ||||||
|  |  | ||||||
|         Worker(byte numberOfCores, int core, byte[] initialHash, byte[] target) { |  | ||||||
|             this.numberOfCores = numberOfCores; |  | ||||||
|             this.initialHash = initialHash; |  | ||||||
|             this.target = target; |  | ||||||
|             this.nonce[7] = (byte) core; |  | ||||||
|             try { |  | ||||||
|                 mda = MessageDigest.getInstance("SHA-512"); |  | ||||||
|             } catch (NoSuchAlgorithmException e) { |  | ||||||
|                 LOG.error(e.getMessage(), e); |  | ||||||
|                 throw new ApplicationException(e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         @Override |  | ||||||
|         public byte[] call() throws Exception { |  | ||||||
|             do { |  | ||||||
|                 inc(nonce, numberOfCores); |  | ||||||
|                 mda.update(nonce); |  | ||||||
|                 mda.update(initialHash); |  | ||||||
|                 if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) { |  | ||||||
|                     return nonce; |  | ||||||
|                 } |  | ||||||
|             } while (!Thread.interrupted()); |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,54 +0,0 @@ | |||||||
| package ch.dissem.bitmessage.ports; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.net.InetAddress; |  | ||||||
| import java.util.*; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Helper class to kick start node registries. |  | ||||||
|  */ |  | ||||||
| public class NodeRegistryHelper { |  | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(NodeRegistryHelper.class); |  | ||||||
|  |  | ||||||
|     public static Map<Long, Set<NetworkAddress>> loadStableNodes() { |  | ||||||
|         try (InputStream in = NodeRegistryHelper.class.getClassLoader().getResourceAsStream("nodes.txt")) { |  | ||||||
|             Scanner scanner = new Scanner(in); |  | ||||||
|             long stream = 0; |  | ||||||
|             Map<Long, Set<NetworkAddress>> result = new HashMap<>(); |  | ||||||
|             Set<NetworkAddress> streamSet = null; |  | ||||||
|             while (scanner.hasNext()) { |  | ||||||
|                 try { |  | ||||||
|                     String line = scanner.nextLine().trim(); |  | ||||||
|                     if (line.startsWith("[stream")) { |  | ||||||
|                         stream = Long.parseLong(line.substring(8, line.lastIndexOf(']'))); |  | ||||||
|                         streamSet = new HashSet<>(); |  | ||||||
|                         result.put(stream, streamSet); |  | ||||||
|                     } else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) { |  | ||||||
|                         int portIndex = line.lastIndexOf(':'); |  | ||||||
|                         InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex)); |  | ||||||
|                         int port = Integer.valueOf(line.substring(portIndex + 1)); |  | ||||||
|                         for (InetAddress inetAddress : inetAddresses) { |  | ||||||
|                             streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build()); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } catch (IOException e) { |  | ||||||
|                     LOG.warn(e.getMessage(), e); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             if (LOG.isDebugEnabled()) { |  | ||||||
|                 for (Map.Entry<Long, Set<NetworkAddress>> e : result.entrySet()) { |  | ||||||
|                     LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes."); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return result; |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,45 +0,0 @@ | |||||||
| package ch.dissem.bitmessage.ports; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.ObjectMessage; |  | ||||||
| import ch.dissem.bitmessage.entity.Plaintext; |  | ||||||
|  |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Objects that proof of work is currently being done for. |  | ||||||
|  * |  | ||||||
|  * @author Christian Basler |  | ||||||
|  */ |  | ||||||
| public interface ProofOfWorkRepository { |  | ||||||
|     Item getItem(byte[] initialHash); |  | ||||||
|  |  | ||||||
|     List<byte[]> getItems(); |  | ||||||
|  |  | ||||||
|     void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes); |  | ||||||
|  |  | ||||||
|     void putObject(Item item); |  | ||||||
|  |  | ||||||
|     void removeObject(byte[] initialHash); |  | ||||||
|  |  | ||||||
|     class Item { |  | ||||||
|         public final ObjectMessage object; |  | ||||||
|         public final long nonceTrialsPerByte; |  | ||||||
|         public final long extraBytes; |  | ||||||
|  |  | ||||||
|         // Needed for ACK POW calculation |  | ||||||
|         public final Long expirationTime; |  | ||||||
|         public final Plaintext message; |  | ||||||
|  |  | ||||||
|         public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { |  | ||||||
|             this(object, nonceTrialsPerByte, extraBytes, 0, null); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, long expirationTime, Plaintext message) { |  | ||||||
|             this.object = object; |  | ||||||
|             this.nonceTrialsPerByte = nonceTrialsPerByte; |  | ||||||
|             this.extraBytes = extraBytes; |  | ||||||
|             this.expirationTime = expirationTime; |  | ||||||
|             this.message = message; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,50 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.ports; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
| import ch.dissem.bitmessage.utils.Bytes; |  | ||||||
|  |  | ||||||
| import java.security.MessageDigest; |  | ||||||
| import java.security.NoSuchAlgorithmException; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Bytes.inc; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one. |  | ||||||
|  * <p> |  | ||||||
|  * <strong>Warning:</strong> implementations probably depend on POW being asynchronous, that's |  | ||||||
|  * another reason not to use this one. |  | ||||||
|  * </p> |  | ||||||
|  */ |  | ||||||
| public class SimplePOWEngine implements ProofOfWorkEngine { |  | ||||||
|     @Override |  | ||||||
|     public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) { |  | ||||||
|         try { |  | ||||||
|             MessageDigest mda = MessageDigest.getInstance("SHA-512"); |  | ||||||
|             byte[] nonce = new byte[8]; |  | ||||||
|             do { |  | ||||||
|                 inc(nonce); |  | ||||||
|                 mda.update(nonce); |  | ||||||
|                 mda.update(initialHash); |  | ||||||
|             } while (Bytes.lt(target, mda.digest(mda.digest()), 8)); |  | ||||||
|             callback.onNonceCalculated(initialHash, nonce); |  | ||||||
|         } catch (NoSuchAlgorithmException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,168 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2011 Google Inc. |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *    http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.exception.AddressFormatException; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
|  |  | ||||||
| import java.io.UnsupportedEncodingException; |  | ||||||
|  |  | ||||||
| import static java.util.Arrays.copyOfRange; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Base58 encoder and decoder. |  | ||||||
|  * |  | ||||||
|  * @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily. |  | ||||||
|  */ |  | ||||||
| public class Base58 { |  | ||||||
|     private static final int[] INDEXES = new int[128]; |  | ||||||
|     private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); |  | ||||||
|  |  | ||||||
|     static { |  | ||||||
|         for (int i = 0; i < INDEXES.length; i++) { |  | ||||||
|             INDEXES[i] = -1; |  | ||||||
|         } |  | ||||||
|         for (int i = 0; i < ALPHABET.length; i++) { |  | ||||||
|             INDEXES[ALPHABET[i]] = i; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Encodes the given bytes in base58. No checksum is appended. |  | ||||||
|      * |  | ||||||
|      * @param data to encode |  | ||||||
|      * @return base58 encoded input |  | ||||||
|      */ |  | ||||||
|     public static String encode(byte[] data) { |  | ||||||
|         if (data.length == 0) { |  | ||||||
|             return ""; |  | ||||||
|         } |  | ||||||
|         final byte[] bytes = copyOfRange(data, 0, data.length); |  | ||||||
|         // Count leading zeroes. |  | ||||||
|         int zeroCount = 0; |  | ||||||
|         while (zeroCount < bytes.length && bytes[zeroCount] == 0) { |  | ||||||
|             ++zeroCount; |  | ||||||
|         } |  | ||||||
|         // The actual encoding. |  | ||||||
|         byte[] temp = new byte[bytes.length * 2]; |  | ||||||
|         int j = temp.length; |  | ||||||
|  |  | ||||||
|         int startAt = zeroCount; |  | ||||||
|         while (startAt < bytes.length) { |  | ||||||
|             byte mod = divmod58(bytes, startAt); |  | ||||||
|             if (bytes[startAt] == 0) { |  | ||||||
|                 ++startAt; |  | ||||||
|             } |  | ||||||
|             temp[--j] = (byte) ALPHABET[mod]; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Strip extra '1' if there are some after decoding. |  | ||||||
|         while (j < temp.length && temp[j] == ALPHABET[0]) { |  | ||||||
|             ++j; |  | ||||||
|         } |  | ||||||
|         // Add as many leading '1' as there were leading zeros. |  | ||||||
|         while (--zeroCount >= 0) { |  | ||||||
|             temp[--j] = (byte) ALPHABET[0]; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         byte[] output = copyOfRange(temp, j, temp.length); |  | ||||||
|         try { |  | ||||||
|             return new String(output, "US-ASCII"); |  | ||||||
|         } catch (UnsupportedEncodingException e) { |  | ||||||
|             throw new ApplicationException(e);  // Cannot happen. |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] decode(String input) throws AddressFormatException { |  | ||||||
|         if (input.length() == 0) { |  | ||||||
|             return new byte[0]; |  | ||||||
|         } |  | ||||||
|         byte[] input58 = new byte[input.length()]; |  | ||||||
|         // Transform the String to a base58 byte sequence |  | ||||||
|         for (int i = 0; i < input.length(); ++i) { |  | ||||||
|             char c = input.charAt(i); |  | ||||||
|  |  | ||||||
|             int digit58 = -1; |  | ||||||
|             if (c < 128) { |  | ||||||
|                 digit58 = INDEXES[c]; |  | ||||||
|             } |  | ||||||
|             if (digit58 < 0) { |  | ||||||
|                 throw new AddressFormatException("Illegal character " + c + " at " + i); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             input58[i] = (byte) digit58; |  | ||||||
|         } |  | ||||||
|         // Count leading zeroes |  | ||||||
|         int zeroCount = 0; |  | ||||||
|         while (zeroCount < input58.length && input58[zeroCount] == 0) { |  | ||||||
|             ++zeroCount; |  | ||||||
|         } |  | ||||||
|         // The encoding |  | ||||||
|         byte[] temp = new byte[input.length()]; |  | ||||||
|         int j = temp.length; |  | ||||||
|  |  | ||||||
|         int startAt = zeroCount; |  | ||||||
|         while (startAt < input58.length) { |  | ||||||
|             byte mod = divmod256(input58, startAt); |  | ||||||
|             if (input58[startAt] == 0) { |  | ||||||
|                 ++startAt; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             temp[--j] = mod; |  | ||||||
|         } |  | ||||||
|         // Do no add extra leading zeroes, move j to first non null byte. |  | ||||||
|         while (j < temp.length && temp[j] == 0) { |  | ||||||
|             ++j; |  | ||||||
|         } |  | ||||||
|         return copyOfRange(temp, j - zeroCount, temp.length); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // number -> number / 58, returns number % 58 |  | ||||||
|     // |  | ||||||
|     private static byte divmod58(byte[] number, int startAt) { |  | ||||||
|         int remainder = 0; |  | ||||||
|         for (int i = startAt; i < number.length; i++) { |  | ||||||
|             int digit256 = (int) number[i] & 0xFF; |  | ||||||
|             int temp = remainder * 256 + digit256; |  | ||||||
|  |  | ||||||
|             number[i] = (byte) (temp / 58); |  | ||||||
|  |  | ||||||
|             remainder = temp % 58; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return (byte) remainder; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // |  | ||||||
|     // number -> number / 256, returns number % 256 |  | ||||||
|     // |  | ||||||
|     private static byte divmod256(byte[] number58, int startAt) { |  | ||||||
|         int remainder = 0; |  | ||||||
|         for (int i = startAt; i < number58.length; i++) { |  | ||||||
|             int digit58 = (int) number58[i] & 0xFF; |  | ||||||
|             int temp = remainder * 58 + digit58; |  | ||||||
|  |  | ||||||
|             number58[i] = (byte) (temp / 256); |  | ||||||
|  |  | ||||||
|             remainder = temp % 256; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return (byte) remainder; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2015 Christian Basler |  * Copyright 2017 Christian Basler | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @@ -21,6 +21,10 @@ package ch.dissem.bitmessage.utils; | |||||||
|  * This is one part due to the fact that Java doesn't support unsigned numbers, and another |  * This is one part due to the fact that Java doesn't support unsigned numbers, and another | ||||||
|  * part so we don't have to convert between byte arrays and numbers in time critical |  * part so we don't have to convert between byte arrays and numbers in time critical | ||||||
|  * situations. |  * situations. | ||||||
|  |  * <p> | ||||||
|  |  * Note: This class can't yet be ported to Kotlin, as with Kotlin byte + byte = int, which | ||||||
|  |  * would be rather inefficient in our case. | ||||||
|  |  * </p> | ||||||
|  */ |  */ | ||||||
| public class Bytes { | public class Bytes { | ||||||
|     public static final byte BYTE_0x80 = (byte) 0x80; |     public static final byte BYTE_0x80 = (byte) 0x80; | ||||||
|   | |||||||
| @@ -1,71 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Random; |  | ||||||
|  |  | ||||||
| public class Collections { |  | ||||||
|     private final static Random RANDOM = new Random(); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @param count      the number of elements to return (if possible) |  | ||||||
|      * @param collection the collection to take samples from |  | ||||||
|      * @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The |  | ||||||
|      * result is by no means securely random, but should be random enough so not the same objects get selected over |  | ||||||
|      * and over again. |  | ||||||
|      */ |  | ||||||
|     public static <T> List<T> selectRandom(int count, Collection<T> collection) { |  | ||||||
|         ArrayList<T> result = new ArrayList<>(count); |  | ||||||
|         if (collection.size() <= count) { |  | ||||||
|             result.addAll(collection); |  | ||||||
|         } else { |  | ||||||
|             double collectionRest = collection.size(); |  | ||||||
|             double resultRest = count; |  | ||||||
|             int skipMax = (int) Math.ceil(collectionRest / resultRest); |  | ||||||
|             int skip = RANDOM.nextInt(skipMax); |  | ||||||
|             for (T item : collection) { |  | ||||||
|                 collectionRest--; |  | ||||||
|                 if (skip > 0) { |  | ||||||
|                     skip--; |  | ||||||
|                 } else { |  | ||||||
|                     result.add(item); |  | ||||||
|                     resultRest--; |  | ||||||
|                     if (resultRest == 0) { |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                     skipMax = (int) Math.ceil(collectionRest / resultRest); |  | ||||||
|                     skip = RANDOM.nextInt(skipMax); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static <T> T selectRandom(Collection<T> collection) { |  | ||||||
|         int index = RANDOM.nextInt(collection.size()); |  | ||||||
|         for (T item : collection) { |  | ||||||
|             if (index == 0) { |  | ||||||
|                 return item; |  | ||||||
|             } |  | ||||||
|             index--; |  | ||||||
|         } |  | ||||||
|         throw new IllegalArgumentException("Empty collection? Size: " + collection.size()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,142 +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.entity.Plaintext; |  | ||||||
| import ch.dissem.bitmessage.entity.valueobject.InventoryVector; |  | ||||||
| import ch.dissem.bitmessage.ports.MessageRepository; |  | ||||||
|  |  | ||||||
| import java.util.*; |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.regex.Matcher; |  | ||||||
| import java.util.regex.Pattern; |  | ||||||
|  |  | ||||||
| import static java.util.regex.Pattern.CASE_INSENSITIVE; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Service that helps with conversations. |  | ||||||
|  */ |  | ||||||
| public class ConversationService { |  | ||||||
|     private final MessageRepository messageRepository; |  | ||||||
|  |  | ||||||
|     private final Pattern SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE); |  | ||||||
|  |  | ||||||
|     public ConversationService(MessageRepository messageRepository) { |  | ||||||
|         this.messageRepository = messageRepository; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Retrieve the whole conversation from one single message. If the message isn't part |  | ||||||
|      * of a conversation, a singleton list containing the given message is returned. Otherwise |  | ||||||
|      * it's the same as {@link #getConversation(UUID)} |  | ||||||
|      * |  | ||||||
|      * @param message |  | ||||||
|      * @return a list of messages that belong to the same conversation. |  | ||||||
|      */ |  | ||||||
|     public List<Plaintext> getConversation(Plaintext message) { |  | ||||||
|         if (message.getConversationId() == null) { |  | ||||||
|             return Collections.singletonList(message); |  | ||||||
|         } |  | ||||||
|         return getConversation(message.getConversationId()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private LinkedList<Plaintext> sorted(Collection<Plaintext> collection) { |  | ||||||
|         LinkedList<Plaintext> result = new LinkedList<>(collection); |  | ||||||
|         Collections.sort(result, new Comparator<Plaintext>() { |  | ||||||
|             @Override |  | ||||||
|             public int compare(Plaintext o1, Plaintext o2) { |  | ||||||
|                 //noinspection NumberEquality - if both are null (if both are the same, it's a bonus) |  | ||||||
|                 if (o1.getReceived() == o2.getReceived()) { |  | ||||||
|                     return 0; |  | ||||||
|                 } |  | ||||||
|                 if (o1.getReceived() == null) { |  | ||||||
|                     return -1; |  | ||||||
|                 } |  | ||||||
|                 if (o2.getReceived() == null) { |  | ||||||
|                     return 1; |  | ||||||
|                 } |  | ||||||
|                 return -o1.getReceived().compareTo(o2.getReceived()); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public List<Plaintext> getConversation(UUID conversationId) { |  | ||||||
|         LinkedList<Plaintext> messages = sorted(messageRepository.getConversation(conversationId)); |  | ||||||
|         Map<InventoryVector, Plaintext> map = new HashMap<>(messages.size()); |  | ||||||
|         for (Plaintext message : messages) { |  | ||||||
|             if (message.getInventoryVector() != null) { |  | ||||||
|                 map.put(message.getInventoryVector(), message); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         LinkedList<Plaintext> result = new LinkedList<>(); |  | ||||||
|         while (!messages.isEmpty()) { |  | ||||||
|             Plaintext last = messages.poll(); |  | ||||||
|             int pos = lastParentPosition(last, result); |  | ||||||
|             result.add(pos, last); |  | ||||||
|             addAncestors(last, result, messages, map); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getSubject(List<Plaintext> conversation) { |  | ||||||
|         if (conversation.isEmpty()) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         // TODO: this has room for improvement |  | ||||||
|         String subject = conversation.get(0).getSubject(); |  | ||||||
|         Matcher matcher = SUBJECT_PREFIX.matcher(subject); |  | ||||||
|         if (matcher.find()) { |  | ||||||
|             return subject.substring(matcher.end()).trim(); |  | ||||||
|         } |  | ||||||
|         return subject.trim(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private int lastParentPosition(Plaintext child, LinkedList<Plaintext> messages) { |  | ||||||
|         Iterator<Plaintext> plaintextIterator = messages.descendingIterator(); |  | ||||||
|         int i = 0; |  | ||||||
|         while (plaintextIterator.hasNext()) { |  | ||||||
|             Plaintext next = plaintextIterator.next(); |  | ||||||
|             if (isParent(next, child)) { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|             i++; |  | ||||||
|         } |  | ||||||
|         return messages.size() - i; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private boolean isParent(Plaintext item, Plaintext child) { |  | ||||||
|         for (InventoryVector parentId : child.getParents()) { |  | ||||||
|             if (parentId.equals(item.getInventoryVector())) { |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void addAncestors(Plaintext message, LinkedList<Plaintext> result, LinkedList<Plaintext> messages, Map<InventoryVector, Plaintext> map) { |  | ||||||
|         for (InventoryVector parentKey : message.getParents()) { |  | ||||||
|             Plaintext parent = map.remove(parentKey); |  | ||||||
|             if (parent != null) { |  | ||||||
|                 messages.remove(parent); |  | ||||||
|                 result.addFirst(parent); |  | ||||||
|                 addAncestors(parent, result, messages, map); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,48 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.ObjectMessage; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
|  |  | ||||||
| import java.io.File; |  | ||||||
| import java.io.FileOutputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.util.Map; |  | ||||||
|  |  | ||||||
| public class DebugUtils { |  | ||||||
|     private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class); |  | ||||||
|  |  | ||||||
|     public static void saveToFile(ObjectMessage objectMessage) { |  | ||||||
|         try { |  | ||||||
|             File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv"); |  | ||||||
|             f.createNewFile(); |  | ||||||
|             objectMessage.write(new FileOutputStream(f)); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             LOG.debug(e.getMessage(), e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static <K> void inc(Map<K, Integer> map, K key) { |  | ||||||
|         if (map.containsKey(key)) { |  | ||||||
|             map.put(key, map.get(key) + 1); |  | ||||||
|         } else { |  | ||||||
|             map.put(key, 1); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,152 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.AccessCounter.inc; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * This class handles decoding simple types from byte stream, according to |  | ||||||
|  * https://bitmessage.org/wiki/Protocol_specification#Common_structures |  | ||||||
|  */ |  | ||||||
| public class Decode { |  | ||||||
|     public static byte[] shortVarBytes(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         int length = uint16(in, counter); |  | ||||||
|         return bytes(in, length, counter); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] varBytes(InputStream in) throws IOException { |  | ||||||
|         return varBytes(in, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] varBytes(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         int length = (int) varInt(in, counter); |  | ||||||
|         return bytes(in, length, counter); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] bytes(InputStream in, int count) throws IOException { |  | ||||||
|         return bytes(in, count, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] bytes(InputStream in, int count, AccessCounter counter) throws IOException { |  | ||||||
|         byte[] result = new byte[count]; |  | ||||||
|         int off = 0; |  | ||||||
|         while (off < count) { |  | ||||||
|             int read = in.read(result, off, count - off); |  | ||||||
|             if (read < 0) { |  | ||||||
|                 throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off); |  | ||||||
|             } |  | ||||||
|             off += read; |  | ||||||
|         } |  | ||||||
|         inc(counter, count); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long[] varIntList(InputStream in) throws IOException { |  | ||||||
|         int length = (int) varInt(in); |  | ||||||
|         long[] result = new long[length]; |  | ||||||
|  |  | ||||||
|         for (int i = 0; i < length; i++) { |  | ||||||
|             result[i] = varInt(in); |  | ||||||
|         } |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long varInt(InputStream in) throws IOException { |  | ||||||
|         return varInt(in, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long varInt(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         int first = in.read(); |  | ||||||
|         inc(counter); |  | ||||||
|         switch (first) { |  | ||||||
|             case 0xfd: |  | ||||||
|                 return uint16(in, counter); |  | ||||||
|             case 0xfe: |  | ||||||
|                 return uint32(in, counter); |  | ||||||
|             case 0xff: |  | ||||||
|                 return int64(in, counter); |  | ||||||
|             default: |  | ||||||
|                 return first; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static int uint8(InputStream in) throws IOException { |  | ||||||
|         return in.read(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static int uint16(InputStream in) throws IOException { |  | ||||||
|         return uint16(in, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static int uint16(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         inc(counter, 2); |  | ||||||
|         return in.read() << 8 | in.read(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long uint32(InputStream in) throws IOException { |  | ||||||
|         return uint32(in, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long uint32(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         inc(counter, 4); |  | ||||||
|         return in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long uint32(ByteBuffer in) { |  | ||||||
|         return u(in.get()) << 24 | u(in.get()) << 16 | u(in.get()) << 8 | u(in.get()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static int int32(InputStream in) throws IOException { |  | ||||||
|         return int32(in, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static int int32(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         inc(counter, 4); |  | ||||||
|         return ByteBuffer.wrap(bytes(in, 4)).getInt(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long int64(InputStream in) throws IOException { |  | ||||||
|         return int64(in, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long int64(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         inc(counter, 8); |  | ||||||
|         return ByteBuffer.wrap(bytes(in, 8)).getLong(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static String varString(InputStream in) throws IOException { |  | ||||||
|         return varString(in, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static String varString(InputStream in, AccessCounter counter) throws IOException { |  | ||||||
|         int length = (int) varInt(in, counter); |  | ||||||
|         // technically, it says the length in characters, but I think this one might be correct |  | ||||||
|         // otherwise it will get complicated, as we'll need to read UTF-8 char by char... |  | ||||||
|         return new String(bytes(in, length, counter), "utf-8"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Returns the given byte as if it were unsigned. |  | ||||||
|      */ |  | ||||||
|     private static int u(byte b) { |  | ||||||
|         return b & 0xFF; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,207 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.Streamable; |  | ||||||
| import ch.dissem.bitmessage.exception.ApplicationException; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayOutputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.OutputStream; |  | ||||||
| import java.io.UnsupportedEncodingException; |  | ||||||
| import java.nio.Buffer; |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.AccessCounter.inc; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * This class handles encoding simple types from byte stream, according to |  | ||||||
|  * https://bitmessage.org/wiki/Protocol_specification#Common_structures |  | ||||||
|  */ |  | ||||||
| public class Encode { |  | ||||||
|     public static void varIntList(long[] values, OutputStream stream) throws IOException { |  | ||||||
|         varInt(values.length, stream); |  | ||||||
|         for (long value : values) { |  | ||||||
|             varInt(value, stream); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varIntList(long[] values, ByteBuffer buffer) { |  | ||||||
|         varInt(values.length, buffer); |  | ||||||
|         for (long value : values) { |  | ||||||
|             varInt(value, buffer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varInt(long value, OutputStream stream) throws IOException { |  | ||||||
|         varInt(value, stream, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varInt(long value, ByteBuffer buffer) { |  | ||||||
|         if (value < 0) { |  | ||||||
|             // This is due to the fact that Java doesn't really support unsigned values. |  | ||||||
|             // Please be aware that this might be an error due to a smaller negative value being cast to long. |  | ||||||
|             // Normally, negative values shouldn't occur within the protocol, and longs large enough for being |  | ||||||
|             // recognized as negatives aren't realistic. |  | ||||||
|             buffer.put((byte) 0xff); |  | ||||||
|             buffer.putLong(value); |  | ||||||
|         } else if (value < 0xfd) { |  | ||||||
|             buffer.put((byte) value); |  | ||||||
|         } else if (value <= 0xffffL) { |  | ||||||
|             buffer.put((byte) 0xfd); |  | ||||||
|             buffer.putShort((short) value); |  | ||||||
|         } else if (value <= 0xffffffffL) { |  | ||||||
|             buffer.put((byte) 0xfe); |  | ||||||
|             buffer.putInt((int) value); |  | ||||||
|         } else { |  | ||||||
|             buffer.put((byte) 0xff); |  | ||||||
|             buffer.putLong(value); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static byte[] varInt(long value) { |  | ||||||
|         ByteBuffer buffer = ByteBuffer.allocate(9); |  | ||||||
|         varInt(value, buffer); |  | ||||||
|         buffer.flip(); |  | ||||||
|         return Bytes.truncate(buffer.array(), buffer.limit()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException { |  | ||||||
|         ByteBuffer buffer = ByteBuffer.allocate(9); |  | ||||||
|         varInt(value, buffer); |  | ||||||
|         buffer.flip(); |  | ||||||
|         stream.write(buffer.array(), 0, buffer.limit()); |  | ||||||
|         inc(counter, buffer.limit()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int8(long value, OutputStream stream) throws IOException { |  | ||||||
|         int8(value, stream, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int8(long value, OutputStream stream, AccessCounter counter) throws IOException { |  | ||||||
|         stream.write((int) value); |  | ||||||
|         inc(counter); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int16(long value, OutputStream stream) throws IOException { |  | ||||||
|         int16(value, stream, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int16(long value, OutputStream stream, AccessCounter counter) throws IOException { |  | ||||||
|         stream.write(ByteBuffer.allocate(2).putShort((short) value).array()); |  | ||||||
|         inc(counter, 2); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int16(long value, ByteBuffer buffer) { |  | ||||||
|         buffer.putShort((short) value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int32(long value, OutputStream stream) throws IOException { |  | ||||||
|         int32(value, stream, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int32(long value, OutputStream stream, AccessCounter counter) throws IOException { |  | ||||||
|         stream.write(ByteBuffer.allocate(4).putInt((int) value).array()); |  | ||||||
|         inc(counter, 4); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int32(long value, ByteBuffer buffer) { |  | ||||||
|         buffer.putInt((int) value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int64(long value, OutputStream stream) throws IOException { |  | ||||||
|         int64(value, stream, null); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int64(long value, OutputStream stream, AccessCounter counter) throws IOException { |  | ||||||
|         stream.write(ByteBuffer.allocate(8).putLong(value).array()); |  | ||||||
|         inc(counter, 8); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void int64(long value, ByteBuffer buffer) { |  | ||||||
|         buffer.putLong(value); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varString(String value, OutputStream out) throws IOException { |  | ||||||
|         byte[] bytes = value.getBytes("utf-8"); |  | ||||||
|         // Technically, it says the length in characters, but I think this one might be correct. |  | ||||||
|         // It doesn't really matter, as only ASCII characters are being used. |  | ||||||
|         // see also Decode#varString() |  | ||||||
|         varInt(bytes.length, out); |  | ||||||
|         out.write(bytes); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varString(String value, ByteBuffer buffer) { |  | ||||||
|         try { |  | ||||||
|             byte[] bytes = value.getBytes("utf-8"); |  | ||||||
|             // Technically, it says the length in characters, but I think this one might be correct. |  | ||||||
|             // It doesn't really matter, as only ASCII characters are being used. |  | ||||||
|             // see also Decode#varString() |  | ||||||
|             buffer.put(varInt(bytes.length)); |  | ||||||
|             buffer.put(bytes); |  | ||||||
|         } catch (UnsupportedEncodingException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varBytes(byte[] data, OutputStream out) throws IOException { |  | ||||||
|         varInt(data.length, out); |  | ||||||
|         out.write(data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void varBytes(byte[] data, ByteBuffer buffer) { |  | ||||||
|         varInt(data.length, buffer); |  | ||||||
|         buffer.put(data); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Serializes a {@link Streamable} object and returns the byte array. |  | ||||||
|      * |  | ||||||
|      * @param streamable the object to be serialized |  | ||||||
|      * @return an array of bytes representing the given streamable object. |  | ||||||
|      */ |  | ||||||
|     public static byte[] bytes(Streamable streamable) { |  | ||||||
|         if (streamable == null) return null; |  | ||||||
|  |  | ||||||
|         ByteArrayOutputStream stream = new ByteArrayOutputStream(); |  | ||||||
|         try { |  | ||||||
|             streamable.write(stream); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|         return stream.toByteArray(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @param streamable the object to be serialized |  | ||||||
|      * @param padding    the result will be padded such that its length is a multiple of <em>padding</em> |  | ||||||
|      * @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding. |  | ||||||
|      */ |  | ||||||
|     public static byte[] bytes(Streamable streamable, int padding) { |  | ||||||
|         ByteArrayOutputStream stream = new ByteArrayOutputStream(); |  | ||||||
|         try { |  | ||||||
|             streamable.write(stream); |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             throw new ApplicationException(e); |  | ||||||
|         } |  | ||||||
|         int offset = padding - stream.size() % padding; |  | ||||||
|         int length = stream.size() + offset; |  | ||||||
|         byte[] result = new byte[length]; |  | ||||||
|         stream.write(result, offset, stream.size()); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @author Christian Basler |  | ||||||
|  */ |  | ||||||
| public class Numbers { |  | ||||||
|     public static long max(long a, long b) { |  | ||||||
|         return a > b ? a : b; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,91 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.Objects; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now |  | ||||||
|  * used to contain different status information. It is by default displayed in some JSON inspired human readable |  | ||||||
|  * notation, but you might only want to rely on the 'human readable' part. |  | ||||||
|  * <p> |  | ||||||
|  * If you need a real JSON representation, please add a method <code>toJson()</code>. |  | ||||||
|  * </p> |  | ||||||
|  */ |  | ||||||
| public class Property { |  | ||||||
|     private String name; |  | ||||||
|     private Object value; |  | ||||||
|     private Property[] properties; |  | ||||||
|  |  | ||||||
|     public Property(String name, Object value, Property... properties) { |  | ||||||
|         this.name = name; |  | ||||||
|         this.value = value; |  | ||||||
|         this.properties = properties; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public String getName() { |  | ||||||
|         return name; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Object getValue() { |  | ||||||
|         return value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Returns the property if available or <code>null</code> otherwise. |  | ||||||
|      * Subproperties can be requested by submitting the sequence of properties. |  | ||||||
|      */ |  | ||||||
|     public Property getProperty(String... name) { |  | ||||||
|         if (name == null || name.length == 0) return null; |  | ||||||
|  |  | ||||||
|         for (Property p : properties) { |  | ||||||
|             if (Objects.equals(name[0], p.name)) { |  | ||||||
|                 if (name.length == 1) |  | ||||||
|                     return p; |  | ||||||
|                 else |  | ||||||
|                     return p.getProperty(Arrays.copyOfRange(name, 1, name.length)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public Property[] getProperties() { |  | ||||||
|         return properties; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public String toString() { |  | ||||||
|         return toString(""); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private String toString(String indentation) { |  | ||||||
|         StringBuilder result = new StringBuilder(); |  | ||||||
|         result.append(indentation).append(name).append(": "); |  | ||||||
|         if (value != null || properties.length == 0) { |  | ||||||
|             result.append(value); |  | ||||||
|         } |  | ||||||
|         if (properties.length > 0) { |  | ||||||
|             result.append("{\n"); |  | ||||||
|             for (Property property : properties) { |  | ||||||
|                 result.append(property.toString(indentation + "  ")).append('\n'); |  | ||||||
|             } |  | ||||||
|             result.append(indentation).append("}"); |  | ||||||
|         } |  | ||||||
|         return result.toString(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,59 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2016 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import ch.dissem.bitmessage.entity.payload.ObjectType; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.Strings.hex; |  | ||||||
|  |  | ||||||
| public class SqlStrings { |  | ||||||
|     public static StringBuilder join(long... objects) { |  | ||||||
|         StringBuilder streamList = new StringBuilder(); |  | ||||||
|         for (int i = 0; i < objects.length; i++) { |  | ||||||
|             if (i > 0) streamList.append(", "); |  | ||||||
|             streamList.append(objects[i]); |  | ||||||
|         } |  | ||||||
|         return streamList; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static StringBuilder join(byte[]... objects) { |  | ||||||
|         StringBuilder streamList = new StringBuilder(); |  | ||||||
|         for (int i = 0; i < objects.length; i++) { |  | ||||||
|             if (i > 0) streamList.append(", "); |  | ||||||
|             streamList.append(hex(objects[i])); |  | ||||||
|         } |  | ||||||
|         return streamList; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static StringBuilder join(ObjectType... types) { |  | ||||||
|         StringBuilder streamList = new StringBuilder(); |  | ||||||
|         for (int i = 0; i < types.length; i++) { |  | ||||||
|             if (i > 0) streamList.append(", "); |  | ||||||
|             streamList.append(types[i].getNumber()); |  | ||||||
|         } |  | ||||||
|         return streamList; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static StringBuilder join(Enum... types) { |  | ||||||
|         StringBuilder streamList = new StringBuilder(); |  | ||||||
|         for (int i = 0; i < types.length; i++) { |  | ||||||
|             if (i > 0) streamList.append(", "); |  | ||||||
|             streamList.append('\'').append(types[i].name()).append('\''); |  | ||||||
|         } |  | ||||||
|         return streamList; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import static ch.dissem.bitmessage.utils.UnixTime.DAY; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Stores times to live in seconds for different object types. Usually this shouldn't be messed with, but for tests |  | ||||||
|  * it might be a good idea to reduce it to a minimum, and on mobile clients you might want to optimize it as well. |  | ||||||
|  * |  | ||||||
|  * @author Christian Basler |  | ||||||
|  */ |  | ||||||
| public class TTL { |  | ||||||
|     private static long msg = 2 * DAY; |  | ||||||
|     private static long getpubkey = 2 * DAY; |  | ||||||
|     private static long pubkey = 28 * DAY; |  | ||||||
|  |  | ||||||
|     public static long msg() { |  | ||||||
|         return msg; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void msg(long msg) { |  | ||||||
|         TTL.msg = validate(msg); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long getpubkey() { |  | ||||||
|         return getpubkey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void getpubkey(long getpubkey) { |  | ||||||
|         TTL.getpubkey = validate(getpubkey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static long pubkey() { |  | ||||||
|         return pubkey; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public static void pubkey(long pubkey) { |  | ||||||
|         TTL.pubkey = validate(pubkey); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static long validate(long ttl) { |  | ||||||
|         if (ttl < 0 || ttl > 28 * DAY) throw new IllegalArgumentException("TTL must be between 0 seconds and 28 days"); |  | ||||||
|         return ttl; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,65 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2016 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| import java.util.concurrent.ThreadFactory; |  | ||||||
| import java.util.concurrent.atomic.AtomicInteger; |  | ||||||
|  |  | ||||||
| public class ThreadFactoryBuilder { |  | ||||||
|     private final String namePrefix; |  | ||||||
|     private int prio = Thread.NORM_PRIORITY; |  | ||||||
|     private boolean daemon = false; |  | ||||||
|  |  | ||||||
|     private ThreadFactoryBuilder(String pool) { |  | ||||||
|         this.namePrefix = pool + "-thread-"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     public static ThreadFactoryBuilder pool(String name) { |  | ||||||
|         return new ThreadFactoryBuilder(name); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ThreadFactoryBuilder lowPrio() { |  | ||||||
|         prio = Thread.MIN_PRIORITY; |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ThreadFactoryBuilder daemon() { |  | ||||||
|         daemon = true; |  | ||||||
|         return this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public ThreadFactory build() { |  | ||||||
|         SecurityManager s = System.getSecurityManager(); |  | ||||||
|         final ThreadGroup group = (s != null) ? s.getThreadGroup() : |  | ||||||
|                 Thread.currentThread().getThreadGroup(); |  | ||||||
|  |  | ||||||
|         return new ThreadFactory() { |  | ||||||
|             private final AtomicInteger threadNumber = new AtomicInteger(1); |  | ||||||
|  |  | ||||||
|             @Override |  | ||||||
|             public Thread newThread(Runnable r) { |  | ||||||
|                 Thread t = new Thread(group, r, |  | ||||||
|                         namePrefix + threadNumber.getAndIncrement(), |  | ||||||
|                         0); |  | ||||||
|                 t.setPriority(prio); |  | ||||||
|                 t.setDaemon(daemon); |  | ||||||
|                 return t; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,52 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright 2015 Christian Basler |  | ||||||
|  * |  | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|  * you may not use this file except in compliance with the License. |  | ||||||
|  * You may obtain a copy of the License at |  | ||||||
|  * |  | ||||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  * |  | ||||||
|  * Unless required by applicable law or agreed to in writing, software |  | ||||||
|  * distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|  * See the License for the specific language governing permissions and |  | ||||||
|  * limitations under the License. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| package ch.dissem.bitmessage.utils; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * A simple utility class that simplifies using the second based time used in Bitmessage. |  | ||||||
|  */ |  | ||||||
| public class UnixTime { |  | ||||||
|     /** |  | ||||||
|      * Length of a minute in seconds, intended for use with {@link #now(long)}. |  | ||||||
|      */ |  | ||||||
|     public static final int MINUTE = 60; |  | ||||||
|     /** |  | ||||||
|      * Length of an hour in seconds, intended for use with {@link #now(long)}. |  | ||||||
|      */ |  | ||||||
|     public static final long HOUR = 60 * MINUTE; |  | ||||||
|     /** |  | ||||||
|      * Length of a day in seconds, intended for use with {@link #now(long)}. |  | ||||||
|      */ |  | ||||||
|     public static final long DAY = 24 * HOUR; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @return the time in second based Unix time ({@link System#currentTimeMillis()}/1000) |  | ||||||
|      */ |  | ||||||
|     public static long now() { |  | ||||||
|         return System.currentTimeMillis() / 1000; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Same as {@link #now()} + shiftSeconds, but might be more readable. |  | ||||||
|      * |  | ||||||
|      * @param shiftSeconds number of seconds from now we're interested in |  | ||||||
|      * @return the Unix time in shiftSeconds seconds / shiftSeconds seconds ago |  | ||||||
|      */ |  | ||||||
|     public static long now(long shiftSeconds) { |  | ||||||
|         return (System.currentTimeMillis() / 1000) + shiftSeconds; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										509
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										509
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,509 @@ | |||||||
|  | /* | ||||||
|  |  * 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() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val isRunning: Boolean | ||||||
|  |         get() = internals.networkHandler.isRunning | ||||||
|  |  | ||||||
|  |     fun addContact(contact: BitmessageAddress) { | ||||||
|  |         internals.addressRepository.save(contact) | ||||||
|  |         if (contact.pubkey == null) { | ||||||
|  |             // If it already existed, the saved contact might have the public key | ||||||
|  |             if (internals.addressRepository.getAddress(contact.address)!!.pubkey == null) { | ||||||
|  |                 internals.requestPubkey(contact) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun addSubscribtion(address: BitmessageAddress) { | ||||||
|  |         address.isSubscribed = true | ||||||
|  |         internals.addressRepository.save(address) | ||||||
|  |         tryToFindBroadcastsForAddress(address) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) { | ||||||
|  |         for (objectMessage in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) { | ||||||
|  |             try { | ||||||
|  |                 val broadcast = objectMessage.payload as Broadcast | ||||||
|  |                 broadcast.decrypt(address) | ||||||
|  |                 // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with | ||||||
|  |                 // other subscriptions and the interface stays as simple as possible. | ||||||
|  |                 internals.networkListener.receive(objectMessage) | ||||||
|  |             } catch (ignore: DecryptionFailedException) { | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 LOG.debug(e.message, e) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun status(): Property { | ||||||
|  |         return Property("status", | ||||||
|  |             Property("user agent", internals.userAgent), | ||||||
|  |             internals.networkHandler.getNetworkStatus(), | ||||||
|  |             Property("unacknowledged", internals.messageRepository.findMessagesToResend().size) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     interface Listener { | ||||||
|  |         fun receive(plaintext: Plaintext) | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * A message listener that needs a [BitmessageContext], i.e. for implementing some sort of chat bot. | ||||||
|  |          */ | ||||||
|  |         interface WithContext : Listener { | ||||||
|  |             fun setContext(ctx: BitmessageContext) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Builder { | ||||||
|  |         internal var port = 8444 | ||||||
|  |         internal var inventory by Delegates.notNull<Inventory>() | ||||||
|  |         internal var nodeRegistry by Delegates.notNull<NodeRegistry>() | ||||||
|  |         internal var networkHandler by Delegates.notNull<NetworkHandler>() | ||||||
|  |         internal var addressRepo by Delegates.notNull<AddressRepository>() | ||||||
|  |         internal var messageRepo by Delegates.notNull<MessageRepository>() | ||||||
|  |         internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>() | ||||||
|  |         internal var proofOfWorkEngine: ProofOfWorkEngine? = null | ||||||
|  |         internal var cryptography by Delegates.notNull<Cryptography>() | ||||||
|  |         internal var customCommandHandler: CustomCommandHandler? = null | ||||||
|  |         internal var labeler: Labeler? = null | ||||||
|  |         internal var userAgent: String? = null | ||||||
|  |         internal var listener by Delegates.notNull<Listener>() | ||||||
|  |         internal var connectionLimit = 150 | ||||||
|  |         internal var connectionTTL = 30 * MINUTE | ||||||
|  |         internal var sendPubkeyOnIdentityCreation = true | ||||||
|  |         internal var doMissingProofOfWorkDelay = 30 | ||||||
|  |  | ||||||
|  |         fun port(port: Int): Builder { | ||||||
|  |             this.port = port | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun inventory(inventory: Inventory): Builder { | ||||||
|  |             this.inventory = inventory | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun nodeRegistry(nodeRegistry: NodeRegistry): Builder { | ||||||
|  |             this.nodeRegistry = nodeRegistry | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun networkHandler(networkHandler: NetworkHandler): Builder { | ||||||
|  |             this.networkHandler = networkHandler | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun addressRepo(addressRepo: AddressRepository): Builder { | ||||||
|  |             this.addressRepo = addressRepo | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun messageRepo(messageRepo: MessageRepository): Builder { | ||||||
|  |             this.messageRepo = messageRepo | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder { | ||||||
|  |             this.proofOfWorkRepository = proofOfWorkRepository | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun cryptography(cryptography: Cryptography): Builder { | ||||||
|  |             this.cryptography = cryptography | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun customCommandHandler(handler: CustomCommandHandler): Builder { | ||||||
|  |             this.customCommandHandler = handler | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun proofOfWorkEngine(proofOfWorkEngine: ProofOfWorkEngine): Builder { | ||||||
|  |             this.proofOfWorkEngine = proofOfWorkEngine | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun labeler(labeler: Labeler): Builder { | ||||||
|  |             this.labeler = labeler | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun listener(listener: Listener): Builder { | ||||||
|  |             this.listener = listener | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @JvmName("kotlinListener") | ||||||
|  |         fun listener(listener: (Plaintext) -> Unit): Builder { | ||||||
|  |             this.listener = object : Listener { | ||||||
|  |                 override fun receive(plaintext: Plaintext) { | ||||||
|  |                     listener.invoke(plaintext) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun connectionLimit(connectionLimit: Int): Builder { | ||||||
|  |             this.connectionLimit = connectionLimit | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun connectionTTL(hours: Int): Builder { | ||||||
|  |             this.connectionTTL = hours * HOUR | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun doMissingProofOfWorkDelay(seconds: Int) { | ||||||
|  |             this.doMissingProofOfWorkDelay = seconds | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * By default a client will send the public key when an identity is being created. On weaker devices | ||||||
|  |          * this behaviour might not be desirable. | ||||||
|  |          */ | ||||||
|  |         fun doNotSendPubkeyOnIdentityCreation(): Builder { | ||||||
|  |             this.sendPubkeyOnIdentityCreation = false | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): BitmessageContext { | ||||||
|  |             return BitmessageContext(this) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         this.labeler = labeler | ||||||
|  |         this.internals = InternalContext( | ||||||
|  |             cryptography, | ||||||
|  |             inventory, | ||||||
|  |             nodeRegistry, | ||||||
|  |             networkHandler, | ||||||
|  |             addressRepository, | ||||||
|  |             messageRepository, | ||||||
|  |             proofOfWorkRepository, | ||||||
|  |             proofOfWorkEngine, | ||||||
|  |             customCommandHandler, | ||||||
|  |             listener, | ||||||
|  |             labeler, | ||||||
|  |             userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/", | ||||||
|  |             port, | ||||||
|  |             connectionTTL, | ||||||
|  |             connectionLimit | ||||||
|  |         ) | ||||||
|  |         this.addresses = addressRepository | ||||||
|  |         this.messages = messageRepository | ||||||
|  |         this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation | ||||||
|  |         (listener as? Listener.WithContext)?.setContext(this) | ||||||
|  |         internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmField val CURRENT_VERSION = 3 | ||||||
|  |         private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java) | ||||||
|  |  | ||||||
|  |         val version: String by lazy { | ||||||
|  |             BitmessageContext::class.java.getResource("/version")?.readText() ?: "local build" | ||||||
|  |         } | ||||||
|  |             @JvmStatic get | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,174 @@ | |||||||
|  | /* | ||||||
|  |  * 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.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 != null && 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 | ||||||
|  |         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") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val LOG = LoggerFactory.getLogger(DefaultMessageListener::class.java) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										226
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.entity.Encrypted | ||||||
|  | import ch.dissem.bitmessage.entity.ObjectMessage | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext | ||||||
|  | import ch.dissem.bitmessage.entity.payload.* | ||||||
|  | import ch.dissem.bitmessage.ports.* | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton | ||||||
|  | import ch.dissem.bitmessage.utils.TTL | ||||||
|  | import ch.dissem.bitmessage.utils.UnixTime | ||||||
|  | import org.slf4j.LoggerFactory | ||||||
|  | import java.util.* | ||||||
|  | import java.util.concurrent.Executors | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The internal context should normally only be used for port implementations. If you need it in your client | ||||||
|  |  * implementation, you're either doing something wrong, something very weird, or the BitmessageContext should | ||||||
|  |  * get extended. | ||||||
|  |  * | ||||||
|  |  * | ||||||
|  |  * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | class InternalContext( | ||||||
|  |     val cryptography: Cryptography, | ||||||
|  |     val inventory: ch.dissem.bitmessage.ports.Inventory, | ||||||
|  |     val nodeRegistry: NodeRegistry, | ||||||
|  |     val networkHandler: NetworkHandler, | ||||||
|  |     val addressRepository: AddressRepository, | ||||||
|  |     val messageRepository: ch.dissem.bitmessage.ports.MessageRepository, | ||||||
|  |     val proofOfWorkRepository: ProofOfWorkRepository, | ||||||
|  |     val proofOfWorkEngine: ProofOfWorkEngine, | ||||||
|  |     val customCommandHandler: CustomCommandHandler, | ||||||
|  |     listener: BitmessageContext.Listener, | ||||||
|  |     val labeler: Labeler, | ||||||
|  |  | ||||||
|  |     val userAgent: String, | ||||||
|  |  | ||||||
|  |     val port: Int, | ||||||
|  |     val connectionTTL: Long, | ||||||
|  |     val connectionLimit: Int | ||||||
|  | ) { | ||||||
|  |  | ||||||
|  |     private val threadPool = Executors.newCachedThreadPool() | ||||||
|  |  | ||||||
|  |     val proofOfWorkService: ProofOfWorkService = ProofOfWorkService() | ||||||
|  |     val networkListener: NetworkHandler.MessageListener = DefaultMessageListener(labeler, listener) | ||||||
|  |     val clientNonce: Long = cryptography.randomNonce() | ||||||
|  |     private val _streams = TreeSet<Long>() | ||||||
|  |     val streams: LongArray | ||||||
|  |         get() = _streams.toLongArray() | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         Singleton.initialize(cryptography) | ||||||
|  |  | ||||||
|  |         // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. | ||||||
|  |         addressRepository.getIdentities().mapTo(_streams) { it.stream } | ||||||
|  |         addressRepository.getSubscriptions().mapTo(_streams) { it.stream } | ||||||
|  |         if (_streams.isEmpty()) { | ||||||
|  |             _streams.add(1L) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, | ||||||
|  |             proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, labeler, | ||||||
|  |             networkListener) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun init(vararg objects: Any) { | ||||||
|  |         objects.filter { it is ContextHolder }.forEach { (it as ContextHolder).setContext(this) } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun send(plaintext: Plaintext) { | ||||||
|  |         if (plaintext.ackMessage != null) { | ||||||
|  |             val expires = UnixTime.now + plaintext.ttl | ||||||
|  |             LOG.info("Expires at " + expires) | ||||||
|  |             proofOfWorkService.doProofOfWorkWithAck(plaintext, expires) | ||||||
|  |         } else { | ||||||
|  |             send(plaintext.from, plaintext.to, Msg(plaintext), plaintext.ttl) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun send(from: BitmessageAddress, to: BitmessageAddress?, payload: ObjectPayload, | ||||||
|  |              timeToLive: Long) { | ||||||
|  |         val recipient = to ?: from | ||||||
|  |         val expires = UnixTime.now + timeToLive | ||||||
|  |         LOG.info("Expires at " + expires) | ||||||
|  |         val objectMessage = ObjectMessage( | ||||||
|  |             stream = recipient.stream, | ||||||
|  |             expiresTime = expires, | ||||||
|  |             payload = payload | ||||||
|  |         ) | ||||||
|  |         if (objectMessage.isSigned) { | ||||||
|  |             objectMessage.sign( | ||||||
|  |                 from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity") | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |         if (payload is Broadcast) { | ||||||
|  |             payload.encrypt() | ||||||
|  |         } else if (payload is Encrypted) { | ||||||
|  |             objectMessage.encrypt( | ||||||
|  |                 recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available") | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |         proofOfWorkService.doProofOfWork(to, objectMessage) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun sendPubkey(identity: BitmessageAddress, targetStream: Long) { | ||||||
|  |         val expires = UnixTime.now + TTL.pubkey | ||||||
|  |         LOG.info("Expires at " + expires) | ||||||
|  |         val payload = identity.pubkey ?: throw IllegalArgumentException("The given address is no identity") | ||||||
|  |         val response = ObjectMessage( | ||||||
|  |             expiresTime = expires, | ||||||
|  |             stream = targetStream, | ||||||
|  |             payload = payload | ||||||
|  |         ) | ||||||
|  |         response.sign( | ||||||
|  |             identity.privateKey ?: throw IllegalArgumentException("The given address is no identity") | ||||||
|  |         ) | ||||||
|  |         response.encrypt(cryptography.createPublicKey(identity.publicDecryptionKey)) | ||||||
|  |         // TODO: remember that the pubkey is just about to be sent, and on which stream! | ||||||
|  |         proofOfWorkService.doProofOfWork(response) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback | ||||||
|  |      * for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB. | ||||||
|  |      */ | ||||||
|  |     fun requestPubkey(contact: BitmessageAddress) { | ||||||
|  |         threadPool.execute { | ||||||
|  |             val stored = addressRepository.getAddress(contact.address) | ||||||
|  |  | ||||||
|  |             tryToFindMatchingPubkey(contact) | ||||||
|  |             if (contact.pubkey != null) { | ||||||
|  |                 if (stored != null) { | ||||||
|  |                     stored.pubkey = contact.pubkey | ||||||
|  |                     addressRepository.save(stored) | ||||||
|  |                 } else { | ||||||
|  |                     addressRepository.save(contact) | ||||||
|  |                 } | ||||||
|  |                 return@execute | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (stored == null) { | ||||||
|  |                 addressRepository.save(contact) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             val expires = UnixTime.now + TTL.getpubkey | ||||||
|  |             LOG.info("Expires at $expires") | ||||||
|  |             val request = ObjectMessage( | ||||||
|  |                 stream = contact.stream, | ||||||
|  |                 expiresTime = expires, | ||||||
|  |                 payload = GetPubkey(contact) | ||||||
|  |             ) | ||||||
|  |             proofOfWorkService.doProofOfWork(request) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun tryToFindMatchingPubkey(address: BitmessageAddress) { | ||||||
|  |         addressRepository.getAddress(address.address)?.let { | ||||||
|  |             address.alias = it.alias | ||||||
|  |             address.isSubscribed = it.isSubscribed | ||||||
|  |         } | ||||||
|  |         for (objectMessage in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) { | ||||||
|  |             try { | ||||||
|  |                 val pubkey = objectMessage.payload as Pubkey | ||||||
|  |                 if (address.version == 4L) { | ||||||
|  |                     val v4Pubkey = pubkey as V4Pubkey | ||||||
|  |                     if (Arrays.equals(address.tag, v4Pubkey.tag)) { | ||||||
|  |                         v4Pubkey.decrypt(address.publicDecryptionKey) | ||||||
|  |                         if (objectMessage.isSignatureValid(v4Pubkey)) { | ||||||
|  |                             address.pubkey = v4Pubkey | ||||||
|  |                             addressRepository.save(address) | ||||||
|  |                             break | ||||||
|  |                         } else { | ||||||
|  |                             LOG.info("Found pubkey for $address but signature is invalid") | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if (Arrays.equals(pubkey.ripe, address.ripe)) { | ||||||
|  |                         address.pubkey = pubkey | ||||||
|  |                         addressRepository.save(address) | ||||||
|  |                         break | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 LOG.debug(e.message, e) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun resendUnacknowledged() { | ||||||
|  |         val messages = messageRepository.findMessagesToResend() | ||||||
|  |         for (message in messages) { | ||||||
|  |             send(message) | ||||||
|  |             messageRepository.save(message) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     interface ContextHolder { | ||||||
|  |         fun setContext(context: InternalContext) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val LOG = LoggerFactory.getLogger(InternalContext::class.java) | ||||||
|  |  | ||||||
|  |         @JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000 | ||||||
|  |         @JvmField val NETWORK_EXTRA_BYTES: Long = 1000 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										121
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES | ||||||
|  | import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE | ||||||
|  | import ch.dissem.bitmessage.entity.* | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Msg | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkEngine | ||||||
|  | import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item | ||||||
|  | import org.slf4j.LoggerFactory | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author Christian Basler | ||||||
|  |  */ | ||||||
|  | class ProofOfWorkService : ProofOfWorkEngine.Callback, InternalContext.ContextHolder { | ||||||
|  |  | ||||||
|  |     private lateinit var ctx: InternalContext | ||||||
|  |     private val cryptography by lazy { ctx.cryptography } | ||||||
|  |     private val powRepo by lazy { ctx.proofOfWorkRepository } | ||||||
|  |     private val messageRepo by lazy { ctx.messageRepository } | ||||||
|  |  | ||||||
|  |     override fun setContext(context: InternalContext) { | ||||||
|  |         ctx = context | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun doMissingProofOfWork(delayInMilliseconds: Long) { | ||||||
|  |         val items = powRepo.getItems() | ||||||
|  |         if (items.isEmpty()) return | ||||||
|  |  | ||||||
|  |         // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU | ||||||
|  |         Timer().schedule(object : TimerTask() { | ||||||
|  |             override fun run() { | ||||||
|  |                 LOG.info("Doing POW for " + items.size + " tasks.") | ||||||
|  |                 for (initialHash in items) { | ||||||
|  |                     val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash) | ||||||
|  |                     cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, | ||||||
|  |                         this@ProofOfWorkService) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, delayInMilliseconds) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun doProofOfWork(objectMessage: ObjectMessage) { | ||||||
|  |         doProofOfWork(null, objectMessage) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun doProofOfWork(recipient: BitmessageAddress?, objectMessage: ObjectMessage) { | ||||||
|  |         val pubkey = recipient?.pubkey | ||||||
|  |  | ||||||
|  |         val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE | ||||||
|  |         val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES | ||||||
|  |  | ||||||
|  |         powRepo.putObject(objectMessage, nonceTrialsPerByte, extraBytes) | ||||||
|  |         if (objectMessage.payload is PlaintextHolder) { | ||||||
|  |             objectMessage.payload.plaintext?.let { | ||||||
|  |                 it.initialHash = cryptography.getInitialHash(objectMessage) | ||||||
|  |                 messageRepo.save(it) | ||||||
|  |             } ?: LOG.error("PlaintextHolder without Plaintext shouldn't make it to the POW") | ||||||
|  |         } | ||||||
|  |         cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, this) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) { | ||||||
|  |         val ack = plaintext.ackMessage!! | ||||||
|  |         messageRepo.save(plaintext) | ||||||
|  |         val item = Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, | ||||||
|  |             expirationTime, plaintext) | ||||||
|  |         powRepo.putObject(item) | ||||||
|  |         cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { | ||||||
|  |         val (objectMessage, _, _, expirationTime, message) = powRepo.getItem(initialHash) | ||||||
|  |         if (message == null) { | ||||||
|  |             objectMessage.nonce = nonce | ||||||
|  |             messageRepo.getMessage(initialHash)?.let { | ||||||
|  |                 it.inventoryVector = objectMessage.inventoryVector | ||||||
|  |                 it.updateNextTry() | ||||||
|  |                 ctx.labeler.markAsSent(it) | ||||||
|  |                 messageRepo.save(it) | ||||||
|  |             } | ||||||
|  |             ctx.inventory.storeObject(objectMessage) | ||||||
|  |             ctx.networkHandler.offer(objectMessage.inventoryVector) | ||||||
|  |         } else { | ||||||
|  |             message.ackMessage!!.nonce = nonce | ||||||
|  |             val newObjectMessage = ObjectMessage.Builder() | ||||||
|  |                 .stream(message.stream) | ||||||
|  |                 .expiresTime(expirationTime!!) | ||||||
|  |                 .payload(Msg(message)) | ||||||
|  |                 .build() | ||||||
|  |             if (newObjectMessage.isSigned) { | ||||||
|  |                 newObjectMessage.sign(message.from.privateKey!!) | ||||||
|  |             } | ||||||
|  |             if (newObjectMessage.payload is Encrypted) { | ||||||
|  |                 newObjectMessage.encrypt(message.to!!.pubkey!!) | ||||||
|  |             } | ||||||
|  |             doProofOfWork(message.to, newObjectMessage) | ||||||
|  |         } | ||||||
|  |         powRepo.removeObject(initialHash) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val LOG = LoggerFactory.getLogger(ProofOfWorkService::class.java) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.constants | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Some network constants | ||||||
|  |  */ | ||||||
|  | object Network { | ||||||
|  |     @JvmField val NETWORK_MAGIC_NUMBER = 8 | ||||||
|  |     @JvmField val HEADER_SIZE = 24 | ||||||
|  |     @JvmField val MAX_PAYLOAD_SIZE = 1600003 | ||||||
|  |     @JvmField val MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.NetworkAddress | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The 'addr' command holds a list of known active Bitmessage nodes. | ||||||
|  |  */ | ||||||
|  | data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload { | ||||||
|  |     override val command: MessagePayload.Command = MessagePayload.Command.ADDR | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         Encode.varInt(addresses.size, out) | ||||||
|  |         for (address in addresses) { | ||||||
|  |             address.write(out) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         Encode.varInt(addresses.size, buffer) | ||||||
|  |         for (address in addresses) { | ||||||
|  |             address.write(buffer) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,195 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Pubkey | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Pubkey.Feature | ||||||
|  | import ch.dissem.bitmessage.entity.payload.V4Pubkey | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.PrivateKey | ||||||
|  | import ch.dissem.bitmessage.utils.AccessCounter | ||||||
|  | import ch.dissem.bitmessage.utils.Base58 | ||||||
|  | import ch.dissem.bitmessage.utils.Bytes | ||||||
|  | import ch.dissem.bitmessage.utils.Decode.bytes | ||||||
|  | import ch.dissem.bitmessage.utils.Decode.varInt | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import java.io.ByteArrayInputStream | ||||||
|  | import java.io.ByteArrayOutputStream | ||||||
|  | import java.io.Serializable | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address | ||||||
|  |  * holding private keys. | ||||||
|  |  */ | ||||||
|  | class BitmessageAddress : Serializable { | ||||||
|  |  | ||||||
|  |     val version: Long | ||||||
|  |     val stream: Long | ||||||
|  |     val ripe: ByteArray | ||||||
|  |     val tag: ByteArray? | ||||||
|  |     /** | ||||||
|  |      * The private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. It's easier to just create | ||||||
|  |      * it regardless of address version. | ||||||
|  |      */ | ||||||
|  |     val publicDecryptionKey: ByteArray | ||||||
|  |  | ||||||
|  |     val address: String | ||||||
|  |  | ||||||
|  |     var privateKey: PrivateKey? = null | ||||||
|  |         private set | ||||||
|  |     var pubkey: Pubkey? = null | ||||||
|  |         set(pubkey) { | ||||||
|  |             if (pubkey != null) { | ||||||
|  |                 if (pubkey is V4Pubkey) { | ||||||
|  |                     if (!Arrays.equals(tag, pubkey.tag)) | ||||||
|  |                         throw IllegalArgumentException("Pubkey has incompatible tag") | ||||||
|  |                 } | ||||||
|  |                 if (!Arrays.equals(ripe, pubkey.ripe)) | ||||||
|  |                     throw IllegalArgumentException("Pubkey has incompatible ripe") | ||||||
|  |                 field = pubkey | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     var alias: String? = null | ||||||
|  |     var isSubscribed: Boolean = false | ||||||
|  |     var isChan: Boolean = false | ||||||
|  |  | ||||||
|  |     internal constructor(version: Long, stream: Long, ripe: ByteArray) { | ||||||
|  |         this.version = version | ||||||
|  |         this.stream = stream | ||||||
|  |         this.ripe = ripe | ||||||
|  |  | ||||||
|  |         val os = ByteArrayOutputStream() | ||||||
|  |         Encode.varInt(version, os) | ||||||
|  |         Encode.varInt(stream, os) | ||||||
|  |         if (version < 4) { | ||||||
|  |             val checksum = cryptography().sha512(os.toByteArray(), ripe) | ||||||
|  |             this.tag = null | ||||||
|  |             this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) | ||||||
|  |         } else { | ||||||
|  |             // for tag and decryption key, the checksum has to be created with 0x00 padding | ||||||
|  |             val checksum = cryptography().doubleSha512(os.toByteArray(), ripe) | ||||||
|  |             this.tag = Arrays.copyOfRange(checksum, 32, 64) | ||||||
|  |             this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) | ||||||
|  |         } | ||||||
|  |         // but for the address and its checksum they need to be stripped | ||||||
|  |         val offset = Bytes.numberOfLeadingZeros(ripe) | ||||||
|  |         os.write(ripe, offset, ripe.size - offset) | ||||||
|  |         val checksum = cryptography().doubleSha512(os.toByteArray()) | ||||||
|  |         os.write(checksum, 0, 4) | ||||||
|  |         this.address = "BM-" + Base58.encode(os.toByteArray()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(publicKey: Pubkey) : this(publicKey.version, publicKey.stream, publicKey.ripe) { | ||||||
|  |         this.pubkey = publicKey | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(address: String, passphrase: String) : this(address) { | ||||||
|  |         val key = PrivateKey(this, passphrase) | ||||||
|  |         if (!Arrays.equals(ripe, key.pubkey.ripe)) { | ||||||
|  |             throw IllegalArgumentException("Wrong address or passphrase") | ||||||
|  |         } | ||||||
|  |         this.privateKey = key | ||||||
|  |         this.pubkey = key.pubkey | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(privateKey: PrivateKey) : this(privateKey.pubkey) { | ||||||
|  |         this.privateKey = privateKey | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(address: String) { | ||||||
|  |         this.address = address | ||||||
|  |         val bytes = Base58.decode(address.substring(3)) | ||||||
|  |         val `in` = ByteArrayInputStream(bytes) | ||||||
|  |         val counter = AccessCounter() | ||||||
|  |         this.version = varInt(`in`, counter) | ||||||
|  |         this.stream = varInt(`in`, counter) | ||||||
|  |         this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20) | ||||||
|  |  | ||||||
|  |         // test checksum | ||||||
|  |         var checksum = cryptography().doubleSha512(bytes, bytes.size - 4) | ||||||
|  |         val expectedChecksum = bytes(`in`, 4) | ||||||
|  |         for (i in 0..3) { | ||||||
|  |             if (expectedChecksum[i] != checksum[i]) | ||||||
|  |                 throw IllegalArgumentException("Checksum of address failed") | ||||||
|  |         } | ||||||
|  |         if (version < 4) { | ||||||
|  |             checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe) | ||||||
|  |             this.tag = null | ||||||
|  |             this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) | ||||||
|  |         } else { | ||||||
|  |             checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe) | ||||||
|  |             this.tag = Arrays.copyOfRange(checksum, 32, 64) | ||||||
|  |             this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return alias ?: address | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is BitmessageAddress) return false | ||||||
|  |         return version == other.version && | ||||||
|  |             stream == other.stream && | ||||||
|  |             Arrays.equals(ripe, other.ripe) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         return Arrays.hashCode(ripe) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun has(feature: Feature?): Boolean { | ||||||
|  |         return feature?.isActive(pubkey?.behaviorBitfield ?: 0) ?: false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun chan(address: String, passphrase: String): BitmessageAddress { | ||||||
|  |             val result = BitmessageAddress(address, passphrase) | ||||||
|  |             result.isChan = true | ||||||
|  |             return result | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @JvmStatic fun chan(stream: Long, passphrase: String): BitmessageAddress { | ||||||
|  |             val privateKey = PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase) | ||||||
|  |             val result = BitmessageAddress(privateKey) | ||||||
|  |             result.isChan = true | ||||||
|  |             return result | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, | ||||||
|  |                                      version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> { | ||||||
|  |             val result = ArrayList<BitmessageAddress>(numberOfAddresses) | ||||||
|  |             val privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter) | ||||||
|  |             for (pk in privateKeys) { | ||||||
|  |                 result.add(BitmessageAddress(pk)) | ||||||
|  |             } | ||||||
|  |             return result | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @JvmStatic fun calculateTag(version: Long, stream: Long, ripe: ByteArray): ByteArray { | ||||||
|  |             val out = ByteArrayOutputStream() | ||||||
|  |             Encode.varInt(version, out) | ||||||
|  |             Encode.varInt(stream, out) | ||||||
|  |             out.write(ripe) | ||||||
|  |             return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,84 @@ | |||||||
|  | /* | ||||||
|  |  * 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" | ||||||
|  |  | ||||||
|  |         fun read(`in`: InputStream, length: Int): CustomMessage { | ||||||
|  |             val counter = AccessCounter() | ||||||
|  |             return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length())) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun error(message: String): CustomMessage { | ||||||
|  |             return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8"))) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         this.isError = COMMAND_ERROR == customCommand | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2015 Christian Basler |  * Copyright 2017 Christian Basler | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @@ -14,19 +14,18 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package ch.dissem.bitmessage.entity; | package ch.dissem.bitmessage.entity | ||||||
| 
 | 
 | ||||||
| import ch.dissem.bitmessage.exception.DecryptionFailedException; | import ch.dissem.bitmessage.exception.DecryptionFailedException | ||||||
| 
 |  | ||||||
| import java.io.IOException; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Used for objects that have encrypted content |  * Used for objects that have encrypted content | ||||||
|  */ |  */ | ||||||
| public interface Encrypted { | interface Encrypted { | ||||||
|     void encrypt(byte[] publicKey) throws IOException; |     fun encrypt(publicKey: ByteArray) | ||||||
| 
 | 
 | ||||||
|     void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException; |     @Throws(DecryptionFailedException::class) | ||||||
|  |     fun decrypt(privateKey: ByteArray) | ||||||
| 
 | 
 | ||||||
|     boolean isDecrypted(); |     val isDecrypted: Boolean | ||||||
| } | } | ||||||
							
								
								
									
										48
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.InventoryVector | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The 'getdata' command is used to request objects from a node. | ||||||
|  |  */ | ||||||
|  | class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload { | ||||||
|  |  | ||||||
|  |     override val command: MessagePayload.Command = MessagePayload.Command.GETDATA | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         Encode.varInt(inventory.size, out) | ||||||
|  |         for (iv in inventory) { | ||||||
|  |             iv.write(out) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         Encode.varInt(inventory.size, buffer) | ||||||
|  |         for (iv in inventory) { | ||||||
|  |             iv.write(buffer) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmField val MAX_INVENTORY_SIZE = 50000 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.InventoryVector | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items. | ||||||
|  |  */ | ||||||
|  | class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload { | ||||||
|  |  | ||||||
|  |     override val command: MessagePayload.Command = MessagePayload.Command.INV | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         Encode.varInt(inventory.size, out) | ||||||
|  |         for (iv in inventory) { | ||||||
|  |             iv.write(out) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         Encode.varInt(inventory.size, buffer) | ||||||
|  |         for (iv in inventory) { | ||||||
|  |             iv.write(buffer) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2015 Christian Basler |  * Copyright 2017 Christian Basler | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @@ -14,15 +14,15 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package ch.dissem.bitmessage.entity; | package ch.dissem.bitmessage.entity | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A command can hold a network message payload |  * A command can hold a network message payload | ||||||
|  */ |  */ | ||||||
| public interface MessagePayload extends Streamable { | interface MessagePayload : Streamable { | ||||||
|     Command getCommand(); |     val command: Command | ||||||
| 
 | 
 | ||||||
|     enum Command { |     enum class Command { | ||||||
|         VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM |         VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -0,0 +1,125 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A network message is exchanged between two nodes. | ||||||
|  |  */ | ||||||
|  | data class NetworkMessage( | ||||||
|  |     /** | ||||||
|  |      * The actual data, a message or an object. Not to be confused with objectPayload. | ||||||
|  |      */ | ||||||
|  |     val payload: MessagePayload | ||||||
|  | ) : Streamable { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * First 4 bytes of sha512(payload) | ||||||
|  |      */ | ||||||
|  |     private fun getChecksum(bytes: ByteArray): ByteArray { | ||||||
|  |         val d = cryptography().sha512(bytes) | ||||||
|  |         return byteArrayOf(d[0], d[1], d[2], d[3]) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         // magic | ||||||
|  |         Encode.int32(MAGIC, out) | ||||||
|  |  | ||||||
|  |         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) | ||||||
|  |         val command = payload.command.name.toLowerCase() | ||||||
|  |         out.write(command.toByteArray(charset("ASCII"))) | ||||||
|  |         for (i in command.length..11) { | ||||||
|  |             out.write(0x0) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val payloadBytes = Encode.bytes(payload) | ||||||
|  |  | ||||||
|  |         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would | ||||||
|  |         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are | ||||||
|  |         // larger than this. | ||||||
|  |         Encode.int32(payloadBytes.size, out) | ||||||
|  |  | ||||||
|  |         // checksum | ||||||
|  |         out.write(getChecksum(payloadBytes)) | ||||||
|  |  | ||||||
|  |         // message payload | ||||||
|  |         out.write(payloadBytes) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * A more efficient implementation of the write method, writing header data to the provided buffer and returning | ||||||
|  |      * a new buffer containing the payload. | ||||||
|  |  | ||||||
|  |      * @param headerBuffer where the header data is written to (24 bytes) | ||||||
|  |      * * | ||||||
|  |      * @return a buffer containing the payload, ready to be read. | ||||||
|  |      */ | ||||||
|  |     fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer { | ||||||
|  |         return ByteBuffer.wrap(writeHeader(headerBuffer)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer] | ||||||
|  |      * and write the header buffer as well as the returned payload buffer into the channel. | ||||||
|  |  | ||||||
|  |      * @param buffer where everything gets written to. Needs to be large enough for the whole message | ||||||
|  |      * *               to be written. | ||||||
|  |      */ | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         val payloadBytes = writeHeader(buffer) | ||||||
|  |         buffer.put(payloadBytes) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun writeHeader(out: ByteBuffer): ByteArray { | ||||||
|  |         // magic | ||||||
|  |         Encode.int32(MAGIC, out) | ||||||
|  |  | ||||||
|  |         // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) | ||||||
|  |         val command = payload.command.name.toLowerCase() | ||||||
|  |         out.put(command.toByteArray(charset("ASCII"))) | ||||||
|  |  | ||||||
|  |         for (i in command.length..11) { | ||||||
|  |             out.put(0.toByte()) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val payloadBytes = Encode.bytes(payload) | ||||||
|  |  | ||||||
|  |         // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would | ||||||
|  |         // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are | ||||||
|  |         // larger than this. | ||||||
|  |         Encode.int32(payloadBytes.size, out) | ||||||
|  |  | ||||||
|  |         // checksum | ||||||
|  |         out.put(getChecksum(payloadBytes)) | ||||||
|  |  | ||||||
|  |         // message payload | ||||||
|  |         return payloadBytes | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         /** | ||||||
|  |          * Magic value indicating message origin network, and used to seek to next message when stream state is unknown | ||||||
|  |          */ | ||||||
|  |         val MAGIC = 0xE9BEB4D9.toInt() | ||||||
|  |         val MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,228 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.payload.ObjectPayload | ||||||
|  | import ch.dissem.bitmessage.entity.payload.ObjectType | ||||||
|  | import ch.dissem.bitmessage.entity.payload.Pubkey | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.InventoryVector | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.PrivateKey | ||||||
|  | import ch.dissem.bitmessage.exception.ApplicationException | ||||||
|  | import ch.dissem.bitmessage.exception.DecryptionFailedException | ||||||
|  | import ch.dissem.bitmessage.utils.Bytes | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import java.io.ByteArrayOutputStream | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The 'object' command sends an object that is shared throughout the network. | ||||||
|  |  */ | ||||||
|  | data class ObjectMessage( | ||||||
|  |     var nonce: ByteArray? = null, | ||||||
|  |     val expiresTime: Long, | ||||||
|  |     val payload: ObjectPayload, | ||||||
|  |     val type: Long, | ||||||
|  |     /** | ||||||
|  |      * The object's version | ||||||
|  |      */ | ||||||
|  |     val version: Long, | ||||||
|  |     val stream: Long | ||||||
|  | ) : MessagePayload { | ||||||
|  |  | ||||||
|  |     override val command: MessagePayload.Command = MessagePayload.Command.OBJECT | ||||||
|  |  | ||||||
|  |     constructor( | ||||||
|  |         nonce: ByteArray? = null, | ||||||
|  |         expiresTime: Long, | ||||||
|  |         payload: ObjectPayload, | ||||||
|  |         stream: Long | ||||||
|  |     ) : this( | ||||||
|  |         nonce, | ||||||
|  |         expiresTime, | ||||||
|  |         payload, | ||||||
|  |         payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"), | ||||||
|  |         payload.version, | ||||||
|  |         stream | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     val inventoryVector: InventoryVector | ||||||
|  |         get() { | ||||||
|  |             return InventoryVector(Bytes.truncate(cryptography().doubleSha512( | ||||||
|  |                 nonce ?: throw IllegalStateException("nonce must be set"), | ||||||
|  |                 payloadBytesWithoutNonce | ||||||
|  |             ), 32)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     private val isEncrypted: Boolean | ||||||
|  |         get() = payload is Encrypted && !payload.isDecrypted | ||||||
|  |  | ||||||
|  |     val isSigned: Boolean | ||||||
|  |         get() = payload.isSigned | ||||||
|  |  | ||||||
|  |     private val bytesToSign: ByteArray | ||||||
|  |         get() { | ||||||
|  |             try { | ||||||
|  |                 val out = ByteArrayOutputStream() | ||||||
|  |                 writeHeaderWithoutNonce(out) | ||||||
|  |                 payload.writeBytesToSign(out) | ||||||
|  |                 return out.toByteArray() | ||||||
|  |             } catch (e: IOException) { | ||||||
|  |                 throw ApplicationException(e) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     fun sign(key: PrivateKey) { | ||||||
|  |         if (payload.isSigned) { | ||||||
|  |             payload.signature = cryptography().getSignature(bytesToSign, key) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(DecryptionFailedException::class) | ||||||
|  |     fun decrypt(key: PrivateKey) { | ||||||
|  |         if (payload is Encrypted) { | ||||||
|  |             payload.decrypt(key.privateEncryptionKey) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(DecryptionFailedException::class) | ||||||
|  |     fun decrypt(privateEncryptionKey: ByteArray) { | ||||||
|  |         if (payload is Encrypted) { | ||||||
|  |             payload.decrypt(privateEncryptionKey) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun encrypt(publicEncryptionKey: ByteArray) { | ||||||
|  |         if (payload is Encrypted) { | ||||||
|  |             payload.encrypt(publicEncryptionKey) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun encrypt(publicKey: Pubkey) { | ||||||
|  |         try { | ||||||
|  |             if (payload is Encrypted) { | ||||||
|  |                 payload.encrypt(publicKey.encryptionKey) | ||||||
|  |             } | ||||||
|  |         } catch (e: IOException) { | ||||||
|  |             throw ApplicationException(e) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun isSignatureValid(pubkey: Pubkey): Boolean { | ||||||
|  |         if (isEncrypted) throw IllegalStateException("Payload must be decrypted first") | ||||||
|  |         return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         out.write(nonce ?: ByteArray(8)) | ||||||
|  |         out.write(payloadBytesWithoutNonce) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         buffer.put(nonce ?: ByteArray(8)) | ||||||
|  |         buffer.put(payloadBytesWithoutNonce) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun writeHeaderWithoutNonce(out: OutputStream) { | ||||||
|  |         Encode.int64(expiresTime, out) | ||||||
|  |         Encode.int32(type, out) | ||||||
|  |         Encode.varInt(version, out) | ||||||
|  |         Encode.varInt(stream, out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val payloadBytesWithoutNonce: ByteArray by lazy { | ||||||
|  |         val out = ByteArrayOutputStream() | ||||||
|  |         writeHeaderWithoutNonce(out) | ||||||
|  |         payload.write(out) | ||||||
|  |         out.toByteArray() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Builder { | ||||||
|  |         private var nonce: ByteArray? = null | ||||||
|  |         private var expiresTime: Long = 0 | ||||||
|  |         private var objectType: Long? = null | ||||||
|  |         private var streamNumber: Long = 0 | ||||||
|  |         private var payload: ObjectPayload? = null | ||||||
|  |  | ||||||
|  |         fun nonce(nonce: ByteArray): Builder { | ||||||
|  |             this.nonce = nonce | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun expiresTime(expiresTime: Long): Builder { | ||||||
|  |             this.expiresTime = expiresTime | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun objectType(objectType: Long): Builder { | ||||||
|  |             this.objectType = objectType | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun objectType(objectType: ObjectType): Builder { | ||||||
|  |             this.objectType = objectType.number | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun stream(streamNumber: Long): Builder { | ||||||
|  |             this.streamNumber = streamNumber | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun payload(payload: ObjectPayload): Builder { | ||||||
|  |             this.payload = payload | ||||||
|  |             if (this.objectType == null) | ||||||
|  |                 this.objectType = payload.type?.number | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): ObjectMessage { | ||||||
|  |             return ObjectMessage( | ||||||
|  |                 nonce = nonce, | ||||||
|  |                 expiresTime = expiresTime, | ||||||
|  |                 type = objectType!!, | ||||||
|  |                 version = payload!!.version, | ||||||
|  |                 stream = if (streamNumber > 0) streamNumber else payload!!.stream, | ||||||
|  |                 payload = payload!! | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is ObjectMessage) return false | ||||||
|  |         return expiresTime == other.expiresTime && | ||||||
|  |             type == other.type && | ||||||
|  |             version == other.version && | ||||||
|  |             stream == other.stream && | ||||||
|  |             payload == other.payload | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         var result = Arrays.hashCode(nonce) | ||||||
|  |         result = 31 * result + (expiresTime xor expiresTime.ushr(32)).toInt() | ||||||
|  |         result = 31 * result + (type xor type.ushr(32)).toInt() | ||||||
|  |         result = 31 * result + (version xor version.ushr(32)).toInt() | ||||||
|  |         result = 31 * result + (stream xor stream.ushr(32)).toInt() | ||||||
|  |         result = 31 * result + (payload.hashCode()) | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										753
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										753
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,753 @@ | |||||||
|  | /* | ||||||
|  |  * 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( | ||||||
|  |         type = builder.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) | ||||||
|  |         if (from.pubkey == null) { | ||||||
|  |             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) | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             Encode.int32(from.pubkey!!.behaviorBitfield, out) | ||||||
|  |             out.write(from.pubkey!!.signingKey, 1, 64) | ||||||
|  |             out.write(from.pubkey!!.encryptionKey, 1, 64) | ||||||
|  |             if (from.version >= 3) { | ||||||
|  |                 Encode.varInt(from.pubkey!!.nonceTrialsPerByte, out) | ||||||
|  |                 Encode.varInt(from.pubkey!!.extraBytes, out) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (type == MSG) { | ||||||
|  |             out.write(to!!.ripe) | ||||||
|  |         } | ||||||
|  |         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 == other.from && | ||||||
|  |             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 | ||||||
|  |         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 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         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 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): Plaintext { | ||||||
|  |             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 (type == MSG && ackMessage == null && ackData == null) { | ||||||
|  |                 ackData = cryptography().randomBytes(Msg.ACK_LENGTH) | ||||||
|  |             } | ||||||
|  |             if (ttl <= 0) { | ||||||
|  |                 ttl = TTL.msg | ||||||
|  |             } | ||||||
|  |             return Plaintext(this) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |  | ||||||
|  |         @JvmStatic fun read(type: Type, `in`: InputStream): Plaintext { | ||||||
|  |             return readWithoutSignature(type, `in`) | ||||||
|  |                 .signature(Decode.varBytes(`in`)) | ||||||
|  |                 .received(UnixTime.now) | ||||||
|  |                 .build() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder { | ||||||
|  |             val version = Decode.varInt(`in`) | ||||||
|  |             return Builder(type) | ||||||
|  |                 .addressVersion(version) | ||||||
|  |                 .stream(Decode.varInt(`in`)) | ||||||
|  |                 .behaviorBitfield(Decode.int32(`in`)) | ||||||
|  |                 .publicSigningKey(Decode.bytes(`in`, 64)) | ||||||
|  |                 .publicEncryptionKey(Decode.bytes(`in`, 64)) | ||||||
|  |                 .nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0) | ||||||
|  |                 .extraBytes(if (version >= 3) Decode.varInt(`in`) else 0) | ||||||
|  |                 .destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null) | ||||||
|  |                 .encoding(Decode.varInt(`in`)) | ||||||
|  |                 .message(Decode.varBytes(`in`)) | ||||||
|  |                 .ackMessage(if (type == MSG) Decode.varBytes(`in`) else null) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2015 Christian Basler |  * Copyright 2017 Christian Basler | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @@ -14,8 +14,8 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package ch.dissem.bitmessage.entity; | package ch.dissem.bitmessage.entity | ||||||
| 
 | 
 | ||||||
| public interface PlaintextHolder { | interface PlaintextHolder { | ||||||
|     Plaintext getPlaintext(); |     val plaintext: Plaintext? | ||||||
| } | } | ||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.io.Serializable | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * An object that can be written to an [OutputStream] | ||||||
|  |  */ | ||||||
|  | interface Streamable : Serializable { | ||||||
|  |     fun write(out: OutputStream) | ||||||
|  |  | ||||||
|  |     fun write(buffer: ByteBuffer) | ||||||
|  | } | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2015 Christian Basler |  * Copyright 2017 Christian Basler | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @@ -14,30 +14,27 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package ch.dissem.bitmessage.entity; | package ch.dissem.bitmessage.entity | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.OutputStream | ||||||
| import java.io.OutputStream; | import java.nio.ByteBuffer | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The 'verack' command answers a 'version' command, accepting the other node's version. |  * The 'verack' command answers a 'version' command, accepting the other node's version. | ||||||
|  */ |  */ | ||||||
| public class VerAck implements MessagePayload { | class VerAck : MessagePayload { | ||||||
|     private static final long serialVersionUID = -4302074845199181687L; |  | ||||||
| 
 | 
 | ||||||
|     @Override |     override val command: MessagePayload.Command = MessagePayload.Command.VERACK | ||||||
|     public Command getCommand() { |  | ||||||
|         return Command.VERACK; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     @Override |     override fun write(out: OutputStream) { | ||||||
|     public void write(OutputStream stream) throws IOException { |  | ||||||
|         // 'verack' doesn't have any payload, so there is nothing to write |         // 'verack' doesn't have any payload, so there is nothing to write | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     override fun write(buffer: ByteBuffer) { | ||||||
|     public void write(ByteBuffer buffer) { |  | ||||||
|         // 'verack' doesn't have any payload, so there is nothing to write |         // 'verack' doesn't have any payload, so there is nothing to write | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         private val serialVersionUID = -4302074845199181687L | ||||||
|  |     } | ||||||
| } | } | ||||||
							
								
								
									
										204
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.BitmessageContext | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.NetworkAddress | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import ch.dissem.bitmessage.utils.UnixTime | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The 'version' command advertises this node's latest supported protocol version upon initiation. | ||||||
|  |  */ | ||||||
|  | class Version constructor( | ||||||
|  |     /** | ||||||
|  |      * Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's | ||||||
|  |      * version is lower but continue with the connection if it is higher. | ||||||
|  |      */ | ||||||
|  |     val version: Int = BitmessageContext.CURRENT_VERSION, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * bitfield of features to be enabled for this connection | ||||||
|  |      */ | ||||||
|  |     val services: Long = Version.Service.getServiceFlag(Version.Service.NODE_NETWORK), | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * standard UNIX timestamp in seconds | ||||||
|  |      */ | ||||||
|  |     val timestamp: Long = UnixTime.now, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The network address of the node receiving this message (not including the time or stream number) | ||||||
|  |      */ | ||||||
|  |     val addrRecv: NetworkAddress, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The network address of the node emitting this message (not including the time or stream number and the ip itself | ||||||
|  |      * is ignored by the receiver) | ||||||
|  |      */ | ||||||
|  |     val addrFrom: NetworkAddress, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Random nonce used to detect connections to self. | ||||||
|  |      */ | ||||||
|  |     val nonce: Long, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes. | ||||||
|  |      */ | ||||||
|  |     val userAgent: String, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000 | ||||||
|  |      * stream numbers. | ||||||
|  |      */ | ||||||
|  |     val streams: LongArray = longArrayOf(1) | ||||||
|  | ) : MessagePayload { | ||||||
|  |  | ||||||
|  |     fun provides(service: Service?): Boolean { | ||||||
|  |         return service != null && service.isEnabled(services) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val command: MessagePayload.Command = MessagePayload.Command.VERSION | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         Encode.int32(version, out) | ||||||
|  |         Encode.int64(services, out) | ||||||
|  |         Encode.int64(timestamp, out) | ||||||
|  |         addrRecv.write(out, true) | ||||||
|  |         addrFrom.write(out, true) | ||||||
|  |         Encode.int64(nonce, out) | ||||||
|  |         Encode.varString(userAgent, out) | ||||||
|  |         Encode.varIntList(streams, out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         Encode.int32(version, buffer) | ||||||
|  |         Encode.int64(services, buffer) | ||||||
|  |         Encode.int64(timestamp, buffer) | ||||||
|  |         addrRecv.write(buffer, true) | ||||||
|  |         addrFrom.write(buffer, true) | ||||||
|  |         Encode.int64(nonce, buffer) | ||||||
|  |         Encode.varString(userAgent, buffer) | ||||||
|  |         Encode.varIntList(streams, buffer) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Builder { | ||||||
|  |         private var version: Int = 0 | ||||||
|  |         private var services: Long = 0 | ||||||
|  |         private var timestamp: Long = 0 | ||||||
|  |         private var addrRecv: NetworkAddress? = null | ||||||
|  |         private var addrFrom: NetworkAddress? = null | ||||||
|  |         private var nonce: Long = 0 | ||||||
|  |         private var userAgent: String? = null | ||||||
|  |         private var streamNumbers: LongArray? = null | ||||||
|  |  | ||||||
|  |         fun defaults(clientNonce: Long): Builder { | ||||||
|  |             version = BitmessageContext.CURRENT_VERSION | ||||||
|  |             services = Service.getServiceFlag(Service.NODE_NETWORK) | ||||||
|  |             timestamp = UnixTime.now | ||||||
|  |             userAgent = "/Jabit:0.0.1/" | ||||||
|  |             streamNumbers = longArrayOf(1) | ||||||
|  |             nonce = clientNonce | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun version(version: Int): Builder { | ||||||
|  |             this.version = version | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun services(vararg services: Service): Builder { | ||||||
|  |             this.services = Service.getServiceFlag(*services) | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun services(services: Long): Builder { | ||||||
|  |             this.services = services | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun timestamp(timestamp: Long): Builder { | ||||||
|  |             this.timestamp = timestamp | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun addrRecv(addrRecv: NetworkAddress): Builder { | ||||||
|  |             this.addrRecv = addrRecv | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun addrFrom(addrFrom: NetworkAddress): Builder { | ||||||
|  |             this.addrFrom = addrFrom | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun nonce(nonce: Long): Builder { | ||||||
|  |             this.nonce = nonce | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun userAgent(userAgent: String): Builder { | ||||||
|  |             this.userAgent = userAgent | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun streams(vararg streamNumbers: Long): Builder { | ||||||
|  |             this.streamNumbers = streamNumbers | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): Version { | ||||||
|  |             val addrRecv = this.addrRecv | ||||||
|  |             val addrFrom = this.addrFrom | ||||||
|  |             if (addrRecv == null || addrFrom == null) { | ||||||
|  |                 throw IllegalStateException("Receiving and sending address must be set") | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return Version( | ||||||
|  |                 version = version, | ||||||
|  |                 services = services, | ||||||
|  |                 timestamp = timestamp, | ||||||
|  |                 addrRecv = addrRecv, addrFrom = addrFrom, | ||||||
|  |                 nonce = nonce, | ||||||
|  |                 userAgent = userAgent ?: "/Jabit:0.0.1/", | ||||||
|  |                 streams = streamNumbers ?: longArrayOf(1) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     enum class Service constructor(internal var flag: Long) { | ||||||
|  |         // TODO: NODE_SSL(2); | ||||||
|  |         NODE_NETWORK(1); | ||||||
|  |  | ||||||
|  |         fun isEnabled(flag: Long): Boolean { | ||||||
|  |             return (flag and this.flag) != 0L | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         companion object { | ||||||
|  |             fun getServiceFlag(vararg services: Service): Long { | ||||||
|  |                 var flag: Long = 0 | ||||||
|  |                 for (service in services) { | ||||||
|  |                     flag = flag or service.flag | ||||||
|  |                 } | ||||||
|  |                 return flag | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.entity.Encrypted | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST | ||||||
|  | import ch.dissem.bitmessage.entity.PlaintextHolder | ||||||
|  | import ch.dissem.bitmessage.exception.DecryptionFailedException | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Users who are subscribed to the sending address will see the message appear in their inbox. | ||||||
|  |  * Broadcasts are version 4 or 5. | ||||||
|  |  */ | ||||||
|  | abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder { | ||||||
|  |  | ||||||
|  |     override val isSigned: Boolean = true | ||||||
|  |  | ||||||
|  |     override var signature: ByteArray? | ||||||
|  |         get() = plaintext?.signature | ||||||
|  |         set(signature) { | ||||||
|  |             plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     override fun encrypt(publicKey: ByteArray) { | ||||||
|  |         this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun encrypt() { | ||||||
|  |         encrypt(cryptography().createPublicKey(plaintext?.from?.publicDecryptionKey ?: return)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(DecryptionFailedException::class) | ||||||
|  |     override fun decrypt(privateKey: ByteArray) { | ||||||
|  |         plaintext = Plaintext.read(BROADCAST, encrypted?.decrypt(privateKey) ?: return) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(DecryptionFailedException::class) | ||||||
|  |     fun decrypt(address: BitmessageAddress) { | ||||||
|  |         decrypt(address.publicDecryptionKey) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val isDecrypted: Boolean | ||||||
|  |         get() = plaintext != null | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is Broadcast) return false | ||||||
|  |         return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         return Objects.hash(stream) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         fun getVersion(address: BitmessageAddress): Long { | ||||||
|  |             return if (address.version < 4) 4L else 5L | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,210 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.Streamable | ||||||
|  | import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE | ||||||
|  | import ch.dissem.bitmessage.exception.DecryptionFailedException | ||||||
|  | import ch.dissem.bitmessage.utils.* | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import org.slf4j.LoggerFactory | ||||||
|  | import java.io.ByteArrayInputStream | ||||||
|  | import java.io.ByteArrayOutputStream | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CryptoBox : Streamable { | ||||||
|  |  | ||||||
|  |     private val initializationVector: ByteArray | ||||||
|  |     private val curveType: Int | ||||||
|  |     private val R: ByteArray | ||||||
|  |     private val mac: ByteArray | ||||||
|  |     private var encrypted: ByteArray | ||||||
|  |  | ||||||
|  |     constructor(data: Streamable, K: ByteArray) : this(Encode.bytes(data), K) | ||||||
|  |  | ||||||
|  |     constructor(data: ByteArray, K: ByteArray) { | ||||||
|  |         curveType = 0x02CA | ||||||
|  |  | ||||||
|  |         // 1. The destination public key is called K. | ||||||
|  |         // 2. Generate 16 random bytes using a secure random number generator. Call them IV. | ||||||
|  |         initializationVector = cryptography().randomBytes(16) | ||||||
|  |  | ||||||
|  |         // 3. Generate a new random EC key pair with private key called r and public key called R. | ||||||
|  |         val r = cryptography().randomBytes(PRIVATE_KEY_SIZE) | ||||||
|  |         R = cryptography().createPublicKey(r) | ||||||
|  |         // 4. Do an EC point multiply with public key K and private key r. This gives you public key P. | ||||||
|  |         val P = cryptography().multiply(K, r) | ||||||
|  |         val X = Points.getX(P) | ||||||
|  |         // 5. Use the X component of public key P and calculate the SHA512 hash H. | ||||||
|  |         val H = cryptography().sha512(X) | ||||||
|  |         // 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. | ||||||
|  |         val key_e = Arrays.copyOfRange(H, 0, 32) | ||||||
|  |         val key_m = Arrays.copyOfRange(H, 32, 64) | ||||||
|  |         // 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. | ||||||
|  |         // 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text. | ||||||
|  |         encrypted = cryptography().crypt(true, data, key_e, initializationVector) | ||||||
|  |         // 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC. | ||||||
|  |         mac = calculateMac(key_m) | ||||||
|  |  | ||||||
|  |         // The resulting data is: IV + R + cipher text + MAC | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private constructor(builder: Builder) { | ||||||
|  |         initializationVector = builder.initializationVector!! | ||||||
|  |         curveType = builder.curveType | ||||||
|  |         R = cryptography().createPoint(builder.xComponent!!, builder.yComponent!!) | ||||||
|  |         encrypted = builder.encrypted!! | ||||||
|  |         mac = builder.mac!! | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param k a private key, typically should be 32 bytes long | ||||||
|  |      * * | ||||||
|  |      * @return an InputStream yielding the decrypted data | ||||||
|  |      * * | ||||||
|  |      * @throws DecryptionFailedException if the payload can't be decrypted using this private key | ||||||
|  |      * * | ||||||
|  |      * @see [https://bitmessage.org/wiki/Encryption.Decryption](https://bitmessage.org/wiki/Encryption.Decryption) | ||||||
|  |      */ | ||||||
|  |     @Throws(DecryptionFailedException::class) | ||||||
|  |     fun decrypt(k: ByteArray): InputStream { | ||||||
|  |         // 1. The private key used to decrypt is called k. | ||||||
|  |         // 2. Do an EC point multiply with private key k and public key R. This gives you public key P. | ||||||
|  |         val P = cryptography().multiply(R, k) | ||||||
|  |         // 3. Use the X component of public key P and calculate the SHA512 hash H. | ||||||
|  |         val H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33)) | ||||||
|  |         // 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. | ||||||
|  |         val key_e = Arrays.copyOfRange(H, 0, 32) | ||||||
|  |         val key_m = Arrays.copyOfRange(H, 32, 64) | ||||||
|  |  | ||||||
|  |         // 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data. | ||||||
|  |         // 6. Compare MAC with MAC'. If not equal, decryption will fail. | ||||||
|  |         if (!Arrays.equals(mac, calculateMac(key_m))) { | ||||||
|  |             throw DecryptionFailedException() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key | ||||||
|  |         //    and the cipher text as payload. The output is the padded input text. | ||||||
|  |         return ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun calculateMac(key_m: ByteArray): ByteArray { | ||||||
|  |         val macData = ByteArrayOutputStream() | ||||||
|  |         writeWithoutMAC(macData) | ||||||
|  |         return cryptography().mac(key_m, macData.toByteArray()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun writeWithoutMAC(out: OutputStream) { | ||||||
|  |         out.write(initializationVector) | ||||||
|  |         Encode.int16(curveType, out) | ||||||
|  |         writeCoordinateComponent(out, Points.getX(R)) | ||||||
|  |         writeCoordinateComponent(out, Points.getY(R)) | ||||||
|  |         out.write(encrypted) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) { | ||||||
|  |         val offset = Bytes.numberOfLeadingZeros(x) | ||||||
|  |         val length = x.size - offset | ||||||
|  |         Encode.int16(length, out) | ||||||
|  |         out.write(x, offset, length) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) { | ||||||
|  |         val offset = Bytes.numberOfLeadingZeros(x) | ||||||
|  |         val length = x.size - offset | ||||||
|  |         Encode.int16(length, buffer) | ||||||
|  |         buffer.put(x, offset, length) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         writeWithoutMAC(out) | ||||||
|  |         out.write(mac) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         buffer.put(initializationVector) | ||||||
|  |         Encode.int16(curveType, buffer) | ||||||
|  |         writeCoordinateComponent(buffer, Points.getX(R)) | ||||||
|  |         writeCoordinateComponent(buffer, Points.getY(R)) | ||||||
|  |         buffer.put(encrypted) | ||||||
|  |         buffer.put(mac) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Builder { | ||||||
|  |         internal var initializationVector: ByteArray? = null | ||||||
|  |         internal var curveType: Int = 0 | ||||||
|  |         internal var xComponent: ByteArray? = null | ||||||
|  |         internal var yComponent: ByteArray? = null | ||||||
|  |         internal var encrypted: ByteArray? = null | ||||||
|  |         internal var mac: ByteArray? = null | ||||||
|  |  | ||||||
|  |         fun IV(initializationVector: ByteArray): Builder { | ||||||
|  |             this.initializationVector = initializationVector | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun curveType(curveType: Int): Builder { | ||||||
|  |             if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType) | ||||||
|  |             this.curveType = curveType | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun X(xComponent: ByteArray): Builder { | ||||||
|  |             this.xComponent = xComponent | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun Y(yComponent: ByteArray): Builder { | ||||||
|  |             this.yComponent = yComponent | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun encrypted(encrypted: ByteArray): Builder { | ||||||
|  |             this.encrypted = encrypted | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun MAC(mac: ByteArray): Builder { | ||||||
|  |             this.mac = mac | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): CryptoBox { | ||||||
|  |             return CryptoBox(this) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private val LOG = LoggerFactory.getLogger(CryptoBox::class.java) | ||||||
|  |  | ||||||
|  |         @JvmStatic fun read(stream: InputStream, length: Int): CryptoBox { | ||||||
|  |             val counter = AccessCounter() | ||||||
|  |             return Builder() | ||||||
|  |                 .IV(Decode.bytes(stream, 16, counter)) | ||||||
|  |                 .curveType(Decode.uint16(stream, counter)) | ||||||
|  |                 .X(Decode.shortVarBytes(stream, counter)) | ||||||
|  |                 .Y(Decode.shortVarBytes(stream, counter)) | ||||||
|  |                 .encrypted(Decode.bytes(stream, length - counter.length() - 32)) | ||||||
|  |                 .MAC(Decode.bytes(stream, 32)) | ||||||
|  |                 .build() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,60 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.utils.Decode | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really | ||||||
|  |  * have to know what it is. | ||||||
|  |  */ | ||||||
|  | class GenericPayload(version: Long, override val stream: Long, val data: ByteArray) : ObjectPayload(version) { | ||||||
|  |  | ||||||
|  |     override val type: ObjectType? = null | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         out.write(data) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         buffer.put(data) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is GenericPayload) return false | ||||||
|  |  | ||||||
|  |         if (stream != other.stream) return false | ||||||
|  |         return Arrays.equals(data, other.data) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         var result = (stream xor stream.ushr(32)).toInt() | ||||||
|  |         result = 31 * result + Arrays.hashCode(data) | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { | ||||||
|  |             return GenericPayload(version, stream, Decode.bytes(`is`, length)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,63 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.utils.Decode | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Request for a public key. | ||||||
|  |  */ | ||||||
|  | class GetPubkey : ObjectPayload { | ||||||
|  |  | ||||||
|  |     override val type: ObjectType = ObjectType.GET_PUBKEY | ||||||
|  |     override val stream: Long | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return an array of bytes that represent either the ripe, or the tag of an address, depending on the | ||||||
|  |      * * address version. | ||||||
|  |      */ | ||||||
|  |     val ripeTag: ByteArray | ||||||
|  |  | ||||||
|  |     constructor(address: BitmessageAddress) : super(address.version) { | ||||||
|  |         this.stream = address.stream | ||||||
|  |         this.ripeTag = if (address.version < 4) address.ripe else | ||||||
|  |             address.tag ?: throw IllegalStateException("Address of version 4 without tag shouldn't exist!") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private constructor(version: Long, stream: Long, ripeOrTag: ByteArray) : super(version) { | ||||||
|  |         this.stream = stream | ||||||
|  |         this.ripeTag = ripeOrTag | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         out.write(ripeTag) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         buffer.put(ripeTag) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { | ||||||
|  |             return GetPubkey(version, stream, Decode.bytes(`is`, length)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.Encrypted | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext.Type.MSG | ||||||
|  | import ch.dissem.bitmessage.entity.PlaintextHolder | ||||||
|  | import ch.dissem.bitmessage.exception.DecryptionFailedException | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Used for person-to-person messages. | ||||||
|  |  */ | ||||||
|  | class Msg : ObjectPayload, Encrypted, PlaintextHolder { | ||||||
|  |  | ||||||
|  |     override val stream: Long | ||||||
|  |     private var encrypted: CryptoBox? | ||||||
|  |     override var plaintext: Plaintext? | ||||||
|  |         private set | ||||||
|  |  | ||||||
|  |     private constructor(stream: Long, encrypted: CryptoBox) : super(1) { | ||||||
|  |         this.stream = stream | ||||||
|  |         this.encrypted = encrypted | ||||||
|  |         this.plaintext = null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(plaintext: Plaintext) : super(1) { | ||||||
|  |         this.stream = plaintext.stream | ||||||
|  |         this.encrypted = null | ||||||
|  |         this.plaintext = plaintext | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val type: ObjectType = ObjectType.MSG | ||||||
|  |  | ||||||
|  |     override val isSigned: Boolean = true | ||||||
|  |  | ||||||
|  |     override fun writeBytesToSign(out: OutputStream) { | ||||||
|  |         plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override var signature: ByteArray? | ||||||
|  |         get() = plaintext?.signature | ||||||
|  |         set(signature) { | ||||||
|  |             plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     override fun encrypt(publicKey: ByteArray) { | ||||||
|  |         this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(DecryptionFailedException::class) | ||||||
|  |     override fun decrypt(privateKey: ByteArray) { | ||||||
|  |         plaintext = Plaintext.read(MSG, encrypted!!.decrypt(privateKey)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val isDecrypted: Boolean | ||||||
|  |         get() = plaintext != null | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is Msg) return false | ||||||
|  |         if (!super.equals(other)) return false | ||||||
|  |  | ||||||
|  |         return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         return stream.toInt() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         val ACK_LENGTH = 32 | ||||||
|  |  | ||||||
|  |         @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg { | ||||||
|  |             return Msg(stream, CryptoBox.read(`in`, length)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.ObjectMessage | ||||||
|  | import ch.dissem.bitmessage.entity.Streamable | ||||||
|  | import java.io.OutputStream | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The payload of an 'object' command. This is shared by the network. | ||||||
|  |  */ | ||||||
|  | abstract class ObjectPayload protected constructor(val version: Long) : Streamable { | ||||||
|  |  | ||||||
|  |     abstract val type: ObjectType? | ||||||
|  |  | ||||||
|  |     abstract val stream: Long | ||||||
|  |  | ||||||
|  |     open val isSigned: Boolean = false | ||||||
|  |  | ||||||
|  |     open fun writeBytesToSign(out: OutputStream) { | ||||||
|  |         // nothing to do | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time, | ||||||
|  |      * * appended with the data described in this table down to the extra_bytes. Therefore, this must | ||||||
|  |      * * be checked and set in the [ObjectMessage] object. | ||||||
|  |      */ | ||||||
|  |     open var signature: ByteArray? = null | ||||||
|  | } | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2015 Christian Basler |  * Copyright 2017 Christian Basler | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @@ -14,31 +14,20 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package ch.dissem.bitmessage.entity.payload; | package ch.dissem.bitmessage.entity.payload | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Known types for 'object' messages. Must not be used where an unknown type must be resent. |  * Known types for 'object' messages. Must not be used where an unknown type must be resent. | ||||||
|  */ |  */ | ||||||
| public enum ObjectType { | enum class ObjectType constructor(val number: Long) { | ||||||
|     GET_PUBKEY(0), |     GET_PUBKEY(0), | ||||||
|     PUBKEY(1), |     PUBKEY(1), | ||||||
|     MSG(2), |     MSG(2), | ||||||
|     BROADCAST(3); |     BROADCAST(3); | ||||||
| 
 | 
 | ||||||
|     int number; |     companion object { | ||||||
| 
 |         @JvmStatic fun fromNumber(number: Long): ObjectType? { | ||||||
|     ObjectType(int number) { |             return values().firstOrNull { it.number == number } | ||||||
|         this.number = number; |         } | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static ObjectType fromNumber(long number) { |  | ||||||
|         for (ObjectType type : values()) { |  | ||||||
|             if (type.number == number) return type; |  | ||||||
|         } |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public long getNumber() { |  | ||||||
|         return number; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -0,0 +1,112 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES | ||||||
|  | import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE | ||||||
|  | import ch.dissem.bitmessage.utils.Singleton.cryptography | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Public keys for signing and encryption, the answer to a 'getpubkey' request. | ||||||
|  |  */ | ||||||
|  | abstract class Pubkey protected constructor(version: Long) : ObjectPayload(version) { | ||||||
|  |  | ||||||
|  |     override val type: ObjectType = ObjectType.PUBKEY | ||||||
|  |  | ||||||
|  |     abstract val signingKey: ByteArray | ||||||
|  |  | ||||||
|  |     abstract val encryptionKey: ByteArray | ||||||
|  |  | ||||||
|  |     abstract val behaviorBitfield: Int | ||||||
|  |  | ||||||
|  |     val ripe: ByteArray by lazy { cryptography().ripemd160(cryptography().sha512(signingKey, encryptionKey)) } | ||||||
|  |  | ||||||
|  |     open val nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE | ||||||
|  |  | ||||||
|  |     open val extraBytes: Long = NETWORK_EXTRA_BYTES | ||||||
|  |  | ||||||
|  |     open fun writeUnencrypted(out: OutputStream) { | ||||||
|  |         write(out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     open fun writeUnencrypted(buffer: ByteBuffer) { | ||||||
|  |         write(buffer) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Bits 0 through 29 are yet undefined | ||||||
|  |      */ | ||||||
|  |     enum class Feature constructor(bitNumber: Int) { | ||||||
|  |         /** | ||||||
|  |          * Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg | ||||||
|  |          * messages bound for them. | ||||||
|  |          */ | ||||||
|  |         INCLUDE_DESTINATION(30), | ||||||
|  |         /** | ||||||
|  |          * If true, the receiving node does send acknowledgements (rather than dropping them). | ||||||
|  |          */ | ||||||
|  |         DOES_ACK(31); | ||||||
|  |  | ||||||
|  |         // The Bitmessage Protocol Specification starts counting at the most significant bit, | ||||||
|  |         // thus the slightly awkward calculation. | ||||||
|  |         // https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features | ||||||
|  |         private val bit: Int = 1 shl 31 - bitNumber | ||||||
|  |  | ||||||
|  |         fun isActive(bitfield: Int): Boolean { | ||||||
|  |             return bitfield and bit != 0 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         companion object { | ||||||
|  |             @JvmStatic fun bitfield(vararg features: Feature): Int { | ||||||
|  |                 var bits = 0 | ||||||
|  |                 for (feature in features) { | ||||||
|  |                     bits = bits or feature.bit | ||||||
|  |                 } | ||||||
|  |                 return bits | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @JvmStatic fun features(bitfield: Int): Array<Feature> { | ||||||
|  |                 val features = ArrayList<Feature>(Feature.values().size) | ||||||
|  |                 for (feature in Feature.values()) { | ||||||
|  |                     if (bitfield and feature.bit != 0) { | ||||||
|  |                         features.add(feature) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return features.toTypedArray() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmField val LATEST_VERSION: Long = 4 | ||||||
|  |  | ||||||
|  |         fun getRipe(publicSigningKey: ByteArray, publicEncryptionKey: ByteArray): ByteArray { | ||||||
|  |             return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun add0x04(key: ByteArray): ByteArray { | ||||||
|  |             if (key.size == 65) return key | ||||||
|  |             val result = ByteArray(65) | ||||||
|  |             result[0] = 4 | ||||||
|  |             System.arraycopy(key, 0, result, 1, 64) | ||||||
|  |             return result | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,93 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.utils.Decode | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A version 2 public key. | ||||||
|  |  */ | ||||||
|  | open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) { | ||||||
|  |  | ||||||
|  |     override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey | ||||||
|  |     override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         Encode.int32(behaviorBitfield, out) | ||||||
|  |         out.write(signingKey, 1, 64) | ||||||
|  |         out.write(encryptionKey, 1, 64) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         Encode.int32(behaviorBitfield, buffer) | ||||||
|  |         buffer.put(signingKey, 1, 64) | ||||||
|  |         buffer.put(encryptionKey, 1, 64) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Builder { | ||||||
|  |         internal var streamNumber: Long = 0 | ||||||
|  |         internal var behaviorBitfield: Int = 0 | ||||||
|  |         internal var publicSigningKey: ByteArray? = null | ||||||
|  |         internal var publicEncryptionKey: ByteArray? = null | ||||||
|  |  | ||||||
|  |         fun stream(streamNumber: Long): Builder { | ||||||
|  |             this.streamNumber = streamNumber | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun behaviorBitfield(behaviorBitfield: Int): Builder { | ||||||
|  |             this.behaviorBitfield = behaviorBitfield | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun publicSigningKey(publicSigningKey: ByteArray): Builder { | ||||||
|  |             this.publicSigningKey = publicSigningKey | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { | ||||||
|  |             this.publicEncryptionKey = publicEncryptionKey | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): V2Pubkey { | ||||||
|  |             return V2Pubkey( | ||||||
|  |                 version = 2, | ||||||
|  |                 stream = streamNumber, | ||||||
|  |                 behaviorBitfield = behaviorBitfield, | ||||||
|  |                 signingKey = add0x04(publicSigningKey!!), | ||||||
|  |                 encryptionKey = add0x04(publicEncryptionKey!!) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey { | ||||||
|  |             return V2Pubkey( | ||||||
|  |                 version = 2, | ||||||
|  |                 stream = stream, | ||||||
|  |                 behaviorBitfield = Decode.uint32(`in`).toInt(), | ||||||
|  |                 signingKey = Decode.bytes(`in`, 64), | ||||||
|  |                 encryptionKey = Decode.bytes(`in`, 64) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,150 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.utils.Decode | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A version 3 public key. | ||||||
|  |  */ | ||||||
|  | class V3Pubkey protected constructor( | ||||||
|  |     version: Long, stream: Long, behaviorBitfield: Int, | ||||||
|  |     signingKey: ByteArray, encryptionKey: ByteArray, | ||||||
|  |     override val nonceTrialsPerByte: Long, | ||||||
|  |     override val extraBytes: Long, | ||||||
|  |     override var signature: ByteArray? = null | ||||||
|  | ) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) { | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         writeBytesToSign(out) | ||||||
|  |         Encode.varBytes( | ||||||
|  |             signature ?: throw IllegalStateException("signature not available"), | ||||||
|  |             out | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         super.write(buffer) | ||||||
|  |         Encode.varInt(nonceTrialsPerByte, buffer) | ||||||
|  |         Encode.varInt(extraBytes, buffer) | ||||||
|  |         Encode.varBytes( | ||||||
|  |             signature ?: throw IllegalStateException("signature not available"), | ||||||
|  |             buffer | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val isSigned: Boolean = true | ||||||
|  |  | ||||||
|  |     override fun writeBytesToSign(out: OutputStream) { | ||||||
|  |         super.write(out) | ||||||
|  |         Encode.varInt(nonceTrialsPerByte, out) | ||||||
|  |         Encode.varInt(extraBytes, out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is V3Pubkey) return false | ||||||
|  |         return nonceTrialsPerByte == other.nonceTrialsPerByte && | ||||||
|  |             extraBytes == other.extraBytes && | ||||||
|  |             stream == other.stream && | ||||||
|  |             behaviorBitfield == other.behaviorBitfield && | ||||||
|  |             Arrays.equals(signingKey, other.signingKey) && | ||||||
|  |             Arrays.equals(encryptionKey, other.encryptionKey) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         return Objects.hash(nonceTrialsPerByte, extraBytes) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Builder { | ||||||
|  |         private var streamNumber: Long = 0 | ||||||
|  |         private var behaviorBitfield: Int = 0 | ||||||
|  |         private var publicSigningKey: ByteArray? = null | ||||||
|  |         private var publicEncryptionKey: ByteArray? = null | ||||||
|  |         private var nonceTrialsPerByte: Long = 0 | ||||||
|  |         private var extraBytes: Long = 0 | ||||||
|  |         private var signature = ByteArray(0) | ||||||
|  |  | ||||||
|  |         fun stream(streamNumber: Long): Builder { | ||||||
|  |             this.streamNumber = streamNumber | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun behaviorBitfield(behaviorBitfield: Int): Builder { | ||||||
|  |             this.behaviorBitfield = behaviorBitfield | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun publicSigningKey(publicSigningKey: ByteArray): Builder { | ||||||
|  |             this.publicSigningKey = publicSigningKey | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { | ||||||
|  |             this.publicEncryptionKey = publicEncryptionKey | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder { | ||||||
|  |             this.nonceTrialsPerByte = nonceTrialsPerByte | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun extraBytes(extraBytes: Long): Builder { | ||||||
|  |             this.extraBytes = extraBytes | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun signature(signature: ByteArray): Builder { | ||||||
|  |             this.signature = signature | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): V3Pubkey { | ||||||
|  |             return V3Pubkey( | ||||||
|  |                 version = 3, | ||||||
|  |                 stream = streamNumber, | ||||||
|  |                 behaviorBitfield = behaviorBitfield, | ||||||
|  |                 signingKey = publicSigningKey!!, | ||||||
|  |                 encryptionKey = publicEncryptionKey!!, | ||||||
|  |                 nonceTrialsPerByte = nonceTrialsPerByte, | ||||||
|  |                 extraBytes = extraBytes, | ||||||
|  |                 signature = signature | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey { | ||||||
|  |             return V3Pubkey( | ||||||
|  |                 version = 3, | ||||||
|  |                 stream = stream, | ||||||
|  |                 behaviorBitfield = Decode.int32(`is`), | ||||||
|  |                 signingKey = Decode.bytes(`is`, 64), | ||||||
|  |                 encryptionKey = Decode.bytes(`is`, 64), | ||||||
|  |                 nonceTrialsPerByte = Decode.varInt(`is`), | ||||||
|  |                 extraBytes = Decode.varInt(`is`), | ||||||
|  |                 signature = Decode.varBytes(`is`) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,59 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext | ||||||
|  |  | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Users who are subscribed to the sending address will see the message appear in their inbox. | ||||||
|  |  * Broadcasts are version 4 or 5. | ||||||
|  |  */ | ||||||
|  | open class V4Broadcast : Broadcast { | ||||||
|  |  | ||||||
|  |     override val type: ObjectType = ObjectType.BROADCAST | ||||||
|  |  | ||||||
|  |     protected constructor(version: Long, stream: Long, encrypted: CryptoBox?, plaintext: Plaintext?) : super(version, stream, encrypted, plaintext) | ||||||
|  |  | ||||||
|  |     constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(4, senderAddress.stream, null, plaintext) { | ||||||
|  |         if (senderAddress.version >= 4) | ||||||
|  |             throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     override fun writeBytesToSign(out: OutputStream) { | ||||||
|  |         plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { | ||||||
|  |             return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,139 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.entity.Encrypted | ||||||
|  | import ch.dissem.bitmessage.exception.DecryptionFailedException | ||||||
|  | import ch.dissem.bitmessage.utils.Decode | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is | ||||||
|  |  * done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and | ||||||
|  |  * use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them | ||||||
|  |  * to create messages to be used in spam or in flooding attacks. | ||||||
|  |  */ | ||||||
|  | class V4Pubkey : Pubkey, Encrypted { | ||||||
|  |  | ||||||
|  |     override val stream: Long | ||||||
|  |     val tag: ByteArray | ||||||
|  |     private var encrypted: CryptoBox? = null | ||||||
|  |     private var decrypted: V3Pubkey? = null | ||||||
|  |  | ||||||
|  |     private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(4) { | ||||||
|  |         this.stream = stream | ||||||
|  |         this.tag = tag | ||||||
|  |         this.encrypted = encrypted | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(decrypted: V3Pubkey) : super(4) { | ||||||
|  |         this.stream = decrypted.stream | ||||||
|  |         this.decrypted = decrypted | ||||||
|  |         this.tag = BitmessageAddress.calculateTag(4, decrypted.stream, decrypted.ripe) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun encrypt(publicKey: ByteArray) { | ||||||
|  |         if (signature == null) throw IllegalStateException("Pubkey must be signed before encryption.") | ||||||
|  |         this.encrypted = CryptoBox(decrypted ?: throw IllegalStateException("no plaintext pubkey data available"), publicKey) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Throws(DecryptionFailedException::class) | ||||||
|  |     override fun decrypt(privateKey: ByteArray) { | ||||||
|  |         decrypted = V3Pubkey.read(encrypted?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"), stream) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val isDecrypted: Boolean | ||||||
|  |         get() = decrypted != null | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         out.write(tag) | ||||||
|  |         encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         buffer.put(tag) | ||||||
|  |         encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun writeUnencrypted(out: OutputStream) { | ||||||
|  |         decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun writeUnencrypted(buffer: ByteBuffer) { | ||||||
|  |         decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun writeBytesToSign(out: OutputStream) { | ||||||
|  |         out.write(tag) | ||||||
|  |         decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override val signingKey: ByteArray | ||||||
|  |         get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |  | ||||||
|  |     override val encryptionKey: ByteArray | ||||||
|  |         get() = decrypted?.encryptionKey ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |  | ||||||
|  |     override val behaviorBitfield: Int | ||||||
|  |         get() = decrypted?.behaviorBitfield ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |  | ||||||
|  |     override var signature: ByteArray? | ||||||
|  |         get() = decrypted?.signature | ||||||
|  |         set(signature) { | ||||||
|  |             decrypted?.signature = signature | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     override val isSigned: Boolean = true | ||||||
|  |  | ||||||
|  |     override val nonceTrialsPerByte: Long | ||||||
|  |         get() = decrypted?.nonceTrialsPerByte ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |  | ||||||
|  |     override val extraBytes: Long | ||||||
|  |         get() = decrypted?.extraBytes ?: throw IllegalStateException("pubkey is encrypted") | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is V4Pubkey) return false | ||||||
|  |  | ||||||
|  |         if (stream != other.stream) return false | ||||||
|  |         if (!Arrays.equals(tag, other.tag)) return false | ||||||
|  |         return !if (decrypted != null) decrypted != other.decrypted else other.decrypted != null | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         var result = (stream xor stream.ushr(32)).toInt() | ||||||
|  |         result = 31 * result + Arrays.hashCode(tag) | ||||||
|  |         result = 31 * result + if (decrypted != null) decrypted!!.hashCode() else 0 | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { | ||||||
|  |             if (encrypted) | ||||||
|  |                 return V4Pubkey(stream, | ||||||
|  |                     Decode.bytes(`in`, 32), | ||||||
|  |                     CryptoBox.read(`in`, length - 32)) | ||||||
|  |             else | ||||||
|  |                 return V4Pubkey(V3Pubkey.read(`in`, stream)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.payload | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.BitmessageAddress | ||||||
|  | import ch.dissem.bitmessage.entity.Plaintext | ||||||
|  | import ch.dissem.bitmessage.utils.Decode | ||||||
|  |  | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.OutputStream | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Users who are subscribed to the sending address will see the message appear in their inbox. | ||||||
|  |  */ | ||||||
|  | class V5Broadcast : V4Broadcast { | ||||||
|  |  | ||||||
|  |     val tag: ByteArray | ||||||
|  |  | ||||||
|  |     private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(5, stream, encrypted, null) { | ||||||
|  |         this.tag = tag | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(5, senderAddress.stream, null, plaintext) { | ||||||
|  |         if (senderAddress.version < 4) | ||||||
|  |             throw IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.version) | ||||||
|  |         this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun writeBytesToSign(out: OutputStream) { | ||||||
|  |         out.write(tag) | ||||||
|  |         super.writeBytesToSign(out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         out.write(tag) | ||||||
|  |         super.write(out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { | ||||||
|  |             return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.valueobject | ||||||
|  |  | ||||||
|  | import ch.dissem.msgpack.types.MPMap | ||||||
|  | import ch.dissem.msgpack.types.MPString | ||||||
|  | import ch.dissem.msgpack.types.MPType | ||||||
|  | import java.io.ByteArrayOutputStream | ||||||
|  | import java.io.Serializable | ||||||
|  | import java.util.zip.DeflaterOutputStream | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Extended encoding message object. | ||||||
|  |  */ | ||||||
|  | data class ExtendedEncoding(val content: ExtendedEncoding.ExtendedType) : Serializable { | ||||||
|  |  | ||||||
|  |     val type: String? = content.type | ||||||
|  |  | ||||||
|  |     fun zip(): ByteArray { | ||||||
|  |         ByteArrayOutputStream().use { out -> | ||||||
|  |             DeflaterOutputStream(out).use { zipper -> content.pack().pack(zipper) } | ||||||
|  |             return out.toByteArray() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     interface Unpacker<out T : ExtendedType> { | ||||||
|  |         val type: String | ||||||
|  |  | ||||||
|  |         fun unpack(map: MPMap<MPString, MPType<*>>): T | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     interface ExtendedType : Serializable { | ||||||
|  |         val type: String | ||||||
|  |  | ||||||
|  |         fun pack(): MPMap<MPString, MPType<*>> | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.valueobject | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.Streamable | ||||||
|  | import ch.dissem.bitmessage.utils.Strings | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | data class InventoryVector constructor( | ||||||
|  |     /** | ||||||
|  |      * Hash of the object | ||||||
|  |      */ | ||||||
|  |     val hash: ByteArray) : Streamable { | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is InventoryVector) return false | ||||||
|  |  | ||||||
|  |         return Arrays.equals(hash, other.hash) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         return Arrays.hashCode(hash) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         out.write(hash) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         buffer.put(hash) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return Strings.hex(hash) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? { | ||||||
|  |             return InventoryVector( | ||||||
|  |                 hash ?: return null | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | /* | ||||||
|  |  * 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 | ||||||
|  | ) : Serializable { | ||||||
|  |  | ||||||
|  |     var id: Any? = null | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return label | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is Label) return false | ||||||
|  |         return label == other.label | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         return Objects.hash(label) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     enum class Type { | ||||||
|  |         INBOX, | ||||||
|  |         BROADCAST, | ||||||
|  |         DRAFT, | ||||||
|  |         OUTBOX, | ||||||
|  |         SENT, | ||||||
|  |         UNREAD, | ||||||
|  |         TRASH | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,199 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright 2017 Christian Basler | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package ch.dissem.bitmessage.entity.valueobject | ||||||
|  |  | ||||||
|  | import ch.dissem.bitmessage.entity.Streamable | ||||||
|  | import ch.dissem.bitmessage.entity.Version | ||||||
|  | import ch.dissem.bitmessage.utils.Encode | ||||||
|  | import ch.dissem.bitmessage.utils.UnixTime | ||||||
|  | import java.io.OutputStream | ||||||
|  | import java.net.InetAddress | ||||||
|  | import java.net.InetSocketAddress | ||||||
|  | import java.net.Socket | ||||||
|  | import java.net.SocketAddress | ||||||
|  | import java.nio.ByteBuffer | ||||||
|  | import java.util.* | ||||||
|  |  | ||||||
|  | fun ip6(inetAddress: InetAddress): ByteArray { | ||||||
|  |     val address = inetAddress.address | ||||||
|  |     when (address.size) { | ||||||
|  |         16 -> { | ||||||
|  |             return address | ||||||
|  |         } | ||||||
|  |         4 -> { | ||||||
|  |             val ip6 = ByteArray(16) | ||||||
|  |             ip6[10] = 0xff.toByte() | ||||||
|  |             ip6[11] = 0xff.toByte() | ||||||
|  |             System.arraycopy(address, 0, ip6, 12, 4) | ||||||
|  |             return ip6 | ||||||
|  |         } | ||||||
|  |         else -> throw IllegalArgumentException("Weird address " + inetAddress) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A node's address. It's written in IPv6 format. | ||||||
|  |  */ | ||||||
|  | data class NetworkAddress( | ||||||
|  |     var time: Long, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Stream number for this node | ||||||
|  |      */ | ||||||
|  |     val stream: Long, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * same service(s) listed in version | ||||||
|  |      */ | ||||||
|  |     val services: Long, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address | ||||||
|  |      * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). | ||||||
|  |      */ | ||||||
|  |     val IPv6: ByteArray, | ||||||
|  |     val port: Int | ||||||
|  | ) : Streamable { | ||||||
|  |  | ||||||
|  |     constructor(time: Long, stream: Long, services: Long = 1, socket: Socket) | ||||||
|  |         : this(time, stream, services, ip6(socket.inetAddress), socket.port) | ||||||
|  |  | ||||||
|  |     constructor(time: Long, stream: Long, services: Long = 1, inetAddress: InetAddress, port: Int) | ||||||
|  |         : this(time, stream, services, ip6(inetAddress), port) | ||||||
|  |  | ||||||
|  |     fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false | ||||||
|  |  | ||||||
|  |     fun toInetAddress(): InetAddress { | ||||||
|  |         return InetAddress.getByAddress(IPv6) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is NetworkAddress) return false | ||||||
|  |  | ||||||
|  |         return port == other.port && Arrays.equals(IPv6, other.IPv6) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         var result = Arrays.hashCode(IPv6) | ||||||
|  |         result = 31 * result + port | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return "[" + toInetAddress() + "]:" + port | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(out: OutputStream) { | ||||||
|  |         write(out, false) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun write(out: OutputStream, light: Boolean) { | ||||||
|  |         if (!light) { | ||||||
|  |             Encode.int64(time, out) | ||||||
|  |             Encode.int32(stream, out) | ||||||
|  |         } | ||||||
|  |         Encode.int64(services, out) | ||||||
|  |         out.write(IPv6) | ||||||
|  |         Encode.int16(port, out) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun write(buffer: ByteBuffer) { | ||||||
|  |         write(buffer, false) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun write(buffer: ByteBuffer, light: Boolean) { | ||||||
|  |         if (!light) { | ||||||
|  |             Encode.int64(time, buffer) | ||||||
|  |             Encode.int32(stream, buffer) | ||||||
|  |         } | ||||||
|  |         Encode.int64(services, buffer) | ||||||
|  |         buffer.put(IPv6) | ||||||
|  |         Encode.int16(port, buffer) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Builder { | ||||||
|  |         internal var time: Long? = null | ||||||
|  |         internal var stream: Long = 0 | ||||||
|  |         internal var services: Long = 1 | ||||||
|  |         internal var ipv6: ByteArray? = null | ||||||
|  |         internal var port: Int = 0 | ||||||
|  |  | ||||||
|  |         fun time(time: Long): Builder { | ||||||
|  |             this.time = time | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun stream(stream: Long): Builder { | ||||||
|  |             this.stream = stream | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun services(services: Long): Builder { | ||||||
|  |             this.services = services | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun ip(inetAddress: InetAddress): Builder { | ||||||
|  |             ipv6 = ip6(inetAddress) | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun ipv6(ipv6: ByteArray): Builder { | ||||||
|  |             this.ipv6 = ipv6 | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun ipv6(p00: Int, p01: Int, p02: Int, p03: Int, | ||||||
|  |                  p04: Int, p05: Int, p06: Int, p07: Int, | ||||||
|  |                  p08: Int, p09: Int, p10: Int, p11: Int, | ||||||
|  |                  p12: Int, p13: Int, p14: Int, p15: Int): Builder { | ||||||
|  |             this.ipv6 = byteArrayOf(p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte(), p04.toByte(), p05.toByte(), p06.toByte(), p07.toByte(), p08.toByte(), p09.toByte(), p10.toByte(), p11.toByte(), p12.toByte(), p13.toByte(), p14.toByte(), p15.toByte()) | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun ipv4(p00: Int, p01: Int, p02: Int, p03: Int): Builder { | ||||||
|  |             this.ipv6 = byteArrayOf(0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0xff.toByte(), 0xff.toByte(), p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte()) | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun port(port: Int): Builder { | ||||||
|  |             this.port = port | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun address(address: SocketAddress): Builder { | ||||||
|  |             if (address is InetSocketAddress) { | ||||||
|  |                 ip(address.address) | ||||||
|  |                 port(address.port) | ||||||
|  |             } else { | ||||||
|  |                 throw IllegalArgumentException("Unknown type of address: " + address.javaClass) | ||||||
|  |             } | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun build(): NetworkAddress { | ||||||
|  |             return NetworkAddress( | ||||||
|  |                 time ?: UnixTime.now, stream, services, ipv6!!, port | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         @JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,169 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | class PrivateKey : Streamable { | ||||||
|  |  | ||||||
|  |     val privateSigningKey: ByteArray | ||||||
|  |     val privateEncryptionKey: ByteArray | ||||||
|  |  | ||||||
|  |     val pubkey: Pubkey | ||||||
|  |  | ||||||
|  |     constructor(shorter: Boolean, stream: Long, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature) { | ||||||
|  |         var privSK: ByteArray | ||||||
|  |         var pubSK: ByteArray | ||||||
|  |         var privEK: ByteArray | ||||||
|  |         var pubEK: ByteArray | ||||||
|  |         var ripe: ByteArray | ||||||
|  |         do { | ||||||
|  |             privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE) | ||||||
|  |             privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE) | ||||||
|  |             pubSK = cryptography().createPublicKey(privSK) | ||||||
|  |             pubEK = cryptography().createPublicKey(privEK) | ||||||
|  |             ripe = Pubkey.getRipe(pubSK, pubEK) | ||||||
|  |         } while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0) | ||||||
|  |         this.privateSigningKey = privSK | ||||||
|  |         this.privateEncryptionKey = privEK | ||||||
|  |         this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, | ||||||
|  |             nonceTrialsPerByte, extraBytes, *features) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     constructor(privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, pubkey: Pubkey) { | ||||||
|  |         this.privateSigningKey = privateSigningKey | ||||||
|  |         this.privateEncryptionKey = privateEncryptionKey | ||||||
|  |         this.pubkey = pubkey | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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.privateSigningKey = builder.privSK!! | ||||||
|  |         this.privateEncryptionKey = builder.privEK!! | ||||||
|  |         this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!, | ||||||
|  |             InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private 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 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) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user