From 8cbdce6eacffc055b2c8c65a7af76725f5aba2d1 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Tue, 21 Nov 2017 10:44:41 +0100 Subject: [PATCH] Refactored to use StreamableWriter Bumped the msgpack library to 2.0.1 (the 2.0.0 build was fubar) --- build.gradle | 6 +- core/build.gradle | 8 +- .../ch/dissem/bitmessage/BitmessageContext.kt | 1 + .../ch/dissem/bitmessage/entity/Addr.kt | 29 ++- .../dissem/bitmessage/entity/CustomMessage.kt | 60 ++--- .../ch/dissem/bitmessage/entity/GetData.kt | 31 ++- .../kotlin/ch/dissem/bitmessage/entity/Inv.kt | 28 ++- .../bitmessage/entity/NetworkMessage.kt | 151 +++++++------ .../dissem/bitmessage/entity/ObjectMessage.kt | 51 +++-- .../ch/dissem/bitmessage/entity/Plaintext.kt | 210 +++++++++--------- .../ch/dissem/bitmessage/entity/Streamable.kt | 22 +- .../ch/dissem/bitmessage/entity/VerAck.kt | 10 +- .../ch/dissem/bitmessage/entity/Version.kt | 52 ++--- .../bitmessage/entity/payload/Broadcast.kt | 7 +- .../bitmessage/entity/payload/CryptoBox.kt | 79 ++++--- .../entity/payload/GenericPayload.kt | 33 ++- .../bitmessage/entity/payload/GetPubkey.kt | 24 +- .../dissem/bitmessage/entity/payload/Msg.kt | 43 ++-- .../entity/payload/ObjectPayload.kt | 7 +- .../bitmessage/entity/payload/Pubkey.kt | 10 +- .../bitmessage/entity/payload/V2Pubkey.kt | 47 +++- .../bitmessage/entity/payload/V3Pubkey.kt | 58 +++-- .../bitmessage/entity/payload/V4Broadcast.kt | 27 ++- .../bitmessage/entity/payload/V4Pubkey.kt | 58 +++-- .../bitmessage/entity/payload/V5Broadcast.kt | 26 ++- .../entity/valueobject/InventoryVector.kt | 28 ++- .../entity/valueobject/NetworkAddress.kt | 60 ++--- .../entity/valueobject/PrivateKey.kt | 91 ++++---- .../entity/valueobject/extended/Message.kt | 34 +-- .../entity/valueobject/extended/Vote.kt | 13 +- .../factory/ExtendedEncodingFactory.kt | 3 +- .../dissem/bitmessage/ports/NodeRegistry.kt | 9 + .../ch/dissem/bitmessage/utils/DebugUtils.kt | 2 +- .../ch/dissem/bitmessage/utils/Encode.kt | 108 ++++++--- .../bitmessage/entity/SerializationTest.kt | 10 +- .../java/ch/dissem/bitmessage/demo/Main.java | 23 +- .../dissem/bitmessage/TestNodeRegistry.java | 21 +- exports/build.gradle | 6 +- .../bitmessage/exports/ContactExport.kt | 2 +- .../extensions/CryptoCustomMessage.kt | 23 +- .../extensions/pow/ProofOfWorkRequest.kt | 27 ++- .../extensions/CryptoCustomMessageTest.kt | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 54212 -> 54706 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 6 +- .../bitmessage/networking/nio/ConnectionIO.kt | 2 +- .../networking/nio/NioNetworkHandler.kt | 2 +- .../bitmessage/networking/TestNodeRegistry.kt | 12 + .../repository/JdbcAddressRepository.kt | 2 +- .../bitmessage/repository/JdbcHelper.kt | 2 +- .../bitmessage/repository/JdbcNodeRegistry.kt | 57 ++++- 51 files changed, 1004 insertions(+), 625 deletions(-) diff --git a/build.gradle b/build.gradle index 859e80a..6a6bfcf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.4-3' + ext.kotlin_version = '1.1.60' repositories { mavenCentral() } @@ -13,12 +13,12 @@ plugins { } subprojects { + apply plugin: 'io.spring.dependency-management' apply plugin: 'kotlin' apply plugin: 'maven' apply plugin: 'signing' apply plugin: 'jacoco' apply plugin: 'gitflow-version' - apply plugin: 'io.spring.dependency-management' apply plugin: 'com.github.ben-manes.versions' sourceCompatibility = 1.7 @@ -138,7 +138,7 @@ subprojects { entry 'slf4j-simple' } - dependency 'ch.dissem.msgpack:msgpack:2.0.0' + dependency 'ch.dissem.msgpack:msgpack:2.0.1' dependency 'org.bouncycastle:bcprov-jdk15on:1.57' dependency 'com.madgag.spongycastle:prov:1.56.0.0' dependency 'org.apache.commons:commons-lang3:3.6' diff --git a/core/build.gradle b/core/build.gradle index 6af7a84..05e5210 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -25,10 +25,10 @@ artifacts { dependencies { compile 'org.slf4j:slf4j-api' - compile 'ch.dissem.msgpack:msgpack:1.0.0' - testCompile 'junit:junit:4.12' - testCompile 'org.hamcrest:hamcrest-library:1.3' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + compile 'ch.dissem.msgpack:msgpack' + testCompile 'junit:junit' + testCompile 'org.hamcrest:hamcrest-library' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(':cryptography-bc') } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt index 731a30f..df05b6b 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt @@ -272,6 +272,7 @@ class BitmessageContext( */ fun cleanup() { internals.inventory.cleanup() + internals.nodeRegistry.cleanup() } /** diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt index 056dd63..70593c3 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt @@ -25,19 +25,28 @@ import java.nio.ByteBuffer * The 'addr' command holds a list of known active Bitmessage nodes. */ data class Addr constructor(val addresses: List) : 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 writer(): StreamableWriter = Writer(this) - override fun write(buffer: ByteBuffer) { - Encode.varInt(addresses.size, buffer) - for (address in addresses) { - address.write(buffer) + private class Writer( + private val item: Addr + ) : StreamableWriter { + + override fun write(out: OutputStream) { + Encode.varInt(item.addresses.size, out) + for (address in item.addresses) { + address.writer().write(out) + } } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(item.addresses.size, buffer) + for (address in item.addresses) { + address.writer().write(buffer) + } + } + } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/CustomMessage.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/CustomMessage.kt index d6ba4d5..1186a18 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/CustomMessage.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/CustomMessage.kt @@ -33,36 +33,42 @@ open class CustomMessage(val customCommand: String, private val data: ByteArray? override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM - val isError: Boolean + val isError = COMMAND_ERROR == customCommand fun getData(): ByteArray { - if (data != null) { - return data - } else { + return data ?: { val out = ByteArrayOutputStream() - write(out) - return out.toByteArray() - } + writer().write(out) + out.toByteArray() + }.invoke() } - 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 writer(): StreamableWriter = Writer(this) - 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()?") + protected open class Writer( + private val item: CustomMessage + ) : StreamableWriter { + + override fun write(out: OutputStream) { + if (item.data != null) { + Encode.varString(item.customCommand, out) + out.write(item.data) + } else { + throw ApplicationException("Tried to write custom message without data. " + + "Programmer: did you forget to override #write()?") + } } + + override fun write(buffer: ByteBuffer) { + if (item.data != null) { + Encode.varString(item.customCommand, buffer) + buffer.put(item.data) + } else { + throw ApplicationException("Tried to write custom message without data. " + + "Programmer: did you forget to override #write()?") + } + } + } companion object { @@ -75,12 +81,6 @@ open class CustomMessage(val customCommand: String, private val data: ByteArray? } @JvmStatic - fun error(message: String): CustomMessage { - return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8"))) - } - } - - init { - this.isError = COMMAND_ERROR == customCommand + fun error(message: String) = CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8"))) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt index feebea1..455b663 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt @@ -28,21 +28,30 @@ class GetData constructor(var inventory: List) : 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 writer(): StreamableWriter = Writer(this) - override fun write(buffer: ByteBuffer) { - Encode.varInt(inventory.size, buffer) - for (iv in inventory) { - iv.write(buffer) + private class Writer( + private val item: GetData + ) : StreamableWriter { + + override fun write(out: OutputStream) { + Encode.varInt(item.inventory.size, out) + for (iv in item.inventory) { + iv.writer().write(out) + } } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(item.inventory.size, buffer) + for (iv in item.inventory) { + iv.writer().write(buffer) + } + } + } companion object { - @JvmField val MAX_INVENTORY_SIZE = 50000 + @JvmField + val MAX_INVENTORY_SIZE = 50000 } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt index b0c5af9..beb2e85 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt @@ -28,17 +28,25 @@ class Inv constructor(val inventory: List) : 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 writer(): StreamableWriter = Writer(this) - override fun write(buffer: ByteBuffer) { - Encode.varInt(inventory.size, buffer) - for (iv in inventory) { - iv.write(buffer) + private class Writer( + private val item: Inv + ) : StreamableWriter { + + override fun write(out: OutputStream) { + Encode.varInt(item.inventory.size, out) + for (iv in item.inventory) { + iv.writer().write(out) + } } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(item.inventory.size, buffer) + for (iv in item.inventory) { + iv.writer().write(buffer) + } + } + } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt index c1a251d..ba9b44b 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt @@ -18,7 +18,6 @@ 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 @@ -32,87 +31,95 @@ data class NetworkMessage( 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 writer(): Writer = Writer(this) - override fun write(out: OutputStream) { - // magic - Encode.int32(MAGIC, out) + class Writer internal constructor( + private val item: NetworkMessage + ) : StreamableWriter { - // 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) + 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 = item.payload.command.name.toLowerCase() + out.write(command.toByteArray(charset("ASCII"))) + for (i in command.length..11) { + out.write(0x0) + } + + val payloadBytes = Encode.bytes(item.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) } - val payloadBytes = Encode.bytes(payload) + /** + * A more efficient implementation of the write method, writing header data to the provided buffer and returning + * a new buffer containing the 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()) + * @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)) } - val payloadBytes = Encode.bytes(payload) + /** + * For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer] + * and write the header buffer as well as the returned payload buffer into the channel. - // 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) + * @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) + } - // checksum - out.put(getChecksum(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 = item.payload.command.name.toLowerCase() + out.put(command.toByteArray(charset("ASCII"))) + + for (i in command.length..11) { + out.put(0.toByte()) + } + + val payloadBytes = Encode.bytes(item.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 + } + + /** + * 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]) + } - // message payload - return payloadBytes } companion object { diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/ObjectMessage.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/ObjectMessage.kt index 255ca48..3ff438c 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/ObjectMessage.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/ObjectMessage.kt @@ -81,8 +81,8 @@ data class ObjectMessage( get() { try { val out = ByteArrayOutputStream() - writeHeaderWithoutNonce(out) - payload.writeBytesToSign(out) + writer.writeHeaderWithoutNonce(out) + payload.writer().writeBytesToSign(out) return out.toByteArray() } catch (e: IOException) { throw ApplicationException(e) @@ -131,30 +131,39 @@ data class ObjectMessage( 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) + writer.writeHeaderWithoutNonce(out) + payload.writer().write(out) out.toByteArray() } + private val writer = Writer(this) + override fun writer(): StreamableWriter = writer + + private class Writer( + private val item: ObjectMessage + ) : StreamableWriter { + + override fun write(out: OutputStream) { + out.write(item.nonce ?: ByteArray(8)) + out.write(item.payloadBytesWithoutNonce) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(item.nonce ?: ByteArray(8)) + buffer.put(item.payloadBytesWithoutNonce) + } + + internal fun writeHeaderWithoutNonce(out: OutputStream) { + Encode.int64(item.expiresTime, out) + Encode.int32(item.type, out) + Encode.varInt(item.version, out) + Encode.varInt(item.stream, out) + } + + } + class Builder { private var nonce: ByteArray? = null private var expiresTime: Long = 0 diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt index aa28f6e..9797990 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt @@ -260,102 +260,6 @@ class Plaintext private constructor( id = builder.id } - fun write(out: OutputStream, includeSignature: Boolean) { - Encode.varInt(from.version, out) - Encode.varInt(from.stream, out) - from.pubkey?.apply { - Encode.int32(behaviorBitfield, out) - out.write(signingKey, 1, 64) - out.write(encryptionKey, 1, 64) - if (from.version >= 3) { - Encode.varInt(nonceTrialsPerByte, out) - Encode.varInt(extraBytes, out) - } - } ?: { - Encode.int32(0, out) - val empty = ByteArray(64) - out.write(empty) - out.write(empty) - if (from.version >= 3) { - Encode.varInt(0, out) - Encode.varInt(0, out) - } - }.invoke() - if (type == MSG) { - out.write(to?.ripe ?: throw IllegalStateException("No recipient set for message")) - } - Encode.varInt(encodingCode, out) - Encode.varInt(message.size, out) - out.write(message) - if (type == MSG) { - if (to?.has(Feature.DOES_ACK) ?: false) { - val ack = ByteArrayOutputStream() - ackMessage?.write(ack) - Encode.varBytes(ack.toByteArray(), out) - } else { - Encode.varInt(0, out) - } - } - if (includeSignature) { - if (signature == null) { - Encode.varInt(0, out) - } else { - Encode.varBytes(signature!!, out) - } - } - } - - fun write(buffer: ByteBuffer, includeSignature: Boolean) { - Encode.varInt(from.version, buffer) - Encode.varInt(from.stream, buffer) - if (from.pubkey == null) { - Encode.int32(0, buffer) - val empty = ByteArray(64) - buffer.put(empty) - buffer.put(empty) - if (from.version >= 3) { - Encode.varInt(0, buffer) - Encode.varInt(0, buffer) - } - } else { - Encode.int32(from.pubkey!!.behaviorBitfield, buffer) - buffer.put(from.pubkey!!.signingKey, 1, 64) - buffer.put(from.pubkey!!.encryptionKey, 1, 64) - if (from.version >= 3) { - Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer) - Encode.varInt(from.pubkey!!.extraBytes, buffer) - } - } - if (type == MSG) { - buffer.put(to!!.ripe) - } - Encode.varInt(encodingCode, buffer) - Encode.varBytes(message, buffer) - if (type == MSG) { - if (to!!.has(Feature.DOES_ACK) && ackMessage != null) { - Encode.varBytes(Encode.bytes(ackMessage!!), buffer) - } else { - Encode.varInt(0, buffer) - } - } - if (includeSignature) { - val sig = signature - if (sig == null) { - Encode.varInt(0, buffer) - } else { - Encode.varBytes(sig, buffer) - } - } - } - - override fun write(out: OutputStream) { - write(out, true) - } - - override fun write(buffer: ByteBuffer) { - write(buffer, true) - } - fun updateNextTry() { if (to != null) { if (nextTry == null) { @@ -484,6 +388,7 @@ class Plaintext private constructor( } enum class Encoding constructor(code: Long) { + IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3); var code: Long = 0 @@ -495,7 +400,8 @@ class Plaintext private constructor( companion object { - @JvmStatic fun fromCode(code: Long): Encoding? { + @JvmStatic + fun fromCode(code: Long): Encoding? { for (e in values()) { if (e.code == code) { return e @@ -503,12 +409,13 @@ class Plaintext private constructor( } return null } + } } enum class Status { + DRAFT, - // For sent messages PUBKEY_REQUESTED, DOING_PROOF_OF_WORK, SENT, @@ -517,9 +424,110 @@ class Plaintext private constructor( } enum class Type { + MSG, BROADCAST } + fun writer(includeSignature: Boolean): StreamableWriter = Writer(this, includeSignature) + + override fun writer(): StreamableWriter = Writer(this) + + private class Writer( + private val item: Plaintext, + private val includeSignature: Boolean = true + ) : StreamableWriter { + + override fun write(out: OutputStream) { + Encode.varInt(item.from.version, out) + Encode.varInt(item.from.stream, out) + item.from.pubkey?.apply { + Encode.int32(behaviorBitfield, out) + out.write(signingKey, 1, 64) + out.write(encryptionKey, 1, 64) + if (item.from.version >= 3) { + Encode.varInt(nonceTrialsPerByte, out) + Encode.varInt(extraBytes, out) + } + } ?: { + Encode.int32(0, out) + val empty = ByteArray(64) + out.write(empty) + out.write(empty) + if (item.from.version >= 3) { + Encode.varInt(0, out) + Encode.varInt(0, out) + } + }.invoke() + if (item.type == MSG) { + out.write(item.to?.ripe ?: throw IllegalStateException("No recipient set for message")) + } + Encode.varInt(item.encodingCode, out) + Encode.varInt(item.message.size, out) + out.write(item.message) + if (item.type == MSG) { + if (item.to?.has(Feature.DOES_ACK) == true) { + val ack = ByteArrayOutputStream() + item.ackMessage?.writer()?.write(ack) + Encode.varBytes(ack.toByteArray(), out) + } else { + Encode.varInt(0, out) + } + } + if (includeSignature) { + val sig = item.signature + if (sig == null) { + Encode.varInt(0, out) + } else { + Encode.varBytes(sig, out) + } + } + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(item.from.version, buffer) + Encode.varInt(item.from.stream, buffer) + if (item.from.pubkey == null) { + Encode.int32(0, buffer) + val empty = ByteArray(64) + buffer.put(empty) + buffer.put(empty) + if (item.from.version >= 3) { + Encode.varInt(0, buffer) + Encode.varInt(0, buffer) + } + } else { + Encode.int32(item.from.pubkey!!.behaviorBitfield, buffer) + buffer.put(item.from.pubkey!!.signingKey, 1, 64) + buffer.put(item.from.pubkey!!.encryptionKey, 1, 64) + if (item.from.version >= 3) { + Encode.varInt(item.from.pubkey!!.nonceTrialsPerByte, buffer) + Encode.varInt(item.from.pubkey!!.extraBytes, buffer) + } + } + if (item.type == MSG) { + buffer.put(item.to!!.ripe) + } + Encode.varInt(item.encodingCode, buffer) + Encode.varBytes(item.message, buffer) + if (item.type == MSG) { + if (item.to!!.has(Feature.DOES_ACK) && item.ackMessage != null) { + Encode.varBytes(Encode.bytes(item.ackMessage!!), buffer) + } else { + Encode.varInt(0, buffer) + } + } + if (includeSignature) { + val sig = item.signature + if (sig == null) { + Encode.varInt(0, buffer) + } else { + Encode.varBytes(sig, buffer) + } + } + } + + } + class Builder(internal val type: Type) { internal var id: Any? = null internal var inventoryVector: InventoryVector? = null @@ -742,14 +750,16 @@ class Plaintext private constructor( companion object { - @JvmStatic fun read(type: Type, `in`: InputStream): Plaintext { + @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 { + @JvmStatic + fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder { val version = Decode.varInt(`in`) return Builder(type) .addressVersion(version) diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Streamable.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Streamable.kt index b8ad391..c8b939c 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Streamable.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Streamable.kt @@ -24,7 +24,27 @@ import java.nio.ByteBuffer * An object that can be written to an [OutputStream] */ interface Streamable : Serializable { - fun write(out: OutputStream) + fun writer(): StreamableWriter +} +interface SignedStreamable : Streamable { + override fun writer(): SignedStreamableWriter +} + +interface EncryptedStreamable : SignedStreamable { + override fun writer(): EncryptedStreamableWriter +} + +interface StreamableWriter: Serializable { + fun write(out: OutputStream) fun write(buffer: ByteBuffer) } + +interface SignedStreamableWriter : StreamableWriter { + fun writeBytesToSign(out: OutputStream) +} + +interface EncryptedStreamableWriter : SignedStreamableWriter { + fun writeUnencrypted(out: OutputStream) + fun writeUnencrypted(buffer: ByteBuffer) +} diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/VerAck.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/VerAck.kt index 268dabe..07f4a88 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/VerAck.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/VerAck.kt @@ -26,12 +26,12 @@ class VerAck : MessagePayload { override val command: MessagePayload.Command = MessagePayload.Command.VERACK - override fun write(out: OutputStream) { - // 'verack' doesn't have any payload, so there is nothing to write - } + // 'verack' doesn't have any payload, so there is nothing to write + override fun writer(): StreamableWriter = EmptyWriter - override fun write(buffer: ByteBuffer) { - // 'verack' doesn't have any payload, so there is nothing to write + internal object EmptyWriter : StreamableWriter { + override fun write(out: OutputStream) = Unit + override fun write(buffer: ByteBuffer) = Unit } companion object { diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt index 5f65bcc..c7ee0a0 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt @@ -71,32 +71,36 @@ class Version constructor( val streams: LongArray = longArrayOf(1) ) : MessagePayload { - fun provides(service: Service?): Boolean { - return service != null && service.isEnabled(services) - } + fun provides(service: Service?) = service?.isEnabled(services) == true 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 writer(): StreamableWriter = Writer(this) - 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) + private class Writer( + private val item: Version + ) : StreamableWriter { + override fun write(out: OutputStream) { + Encode.int32(item.version, out) + Encode.int64(item.services, out) + Encode.int64(item.timestamp, out) + item.addrRecv.writer(true).write(out) + item.addrFrom.writer(true).write(out) + Encode.int64(item.nonce, out) + Encode.varString(item.userAgent, out) + Encode.varIntList(item.streams, out) + } + + override fun write(buffer: ByteBuffer) { + Encode.int32(item.version, buffer) + Encode.int64(item.services, buffer) + Encode.int64(item.timestamp, buffer) + item.addrRecv.writer(true).write(buffer) + item.addrFrom.writer(true).write(buffer) + Encode.int64(item.nonce, buffer) + Encode.varString(item.userAgent, buffer) + Encode.varIntList(item.streams, buffer) + } } class Builder { @@ -187,9 +191,7 @@ class Version constructor( // TODO: NODE_SSL(2); NODE_NETWORK(1); - fun isEnabled(flag: Long): Boolean { - return (flag and this.flag) != 0L - } + fun isEnabled(flag: Long) = (flag and this.flag) != 0L companion object { fun getServiceFlag(vararg services: Service): Long { diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Broadcast.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Broadcast.kt index 9cc5772..778f9ee 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Broadcast.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Broadcast.kt @@ -29,7 +29,12 @@ 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 { +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 diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/CryptoBox.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/CryptoBox.kt index 29f26fe..ea1dc01 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/CryptoBox.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/CryptoBox.kt @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.entity.payload import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.StreamableWriter import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE import ch.dissem.bitmessage.exception.DecryptionFailedException import ch.dissem.bitmessage.utils.* @@ -108,44 +109,53 @@ class CryptoBox : Streamable { private fun calculateMac(key_m: ByteArray): ByteArray { val macData = ByteArrayOutputStream() - writeWithoutMAC(macData) + writer.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 val writer = Writer(this) + override fun writer(): StreamableWriter = writer - 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 class Writer( + private val item: CryptoBox + ) : StreamableWriter { - 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(item.mac) + } - override fun write(out: OutputStream) { - writeWithoutMAC(out) - out.write(mac) - } + internal fun writeWithoutMAC(out: OutputStream) { + out.write(item.initializationVector) + Encode.int16(item.curveType, out) + writeCoordinateComponent(out, Points.getX(item.R)) + writeCoordinateComponent(out, Points.getY(item.R)) + out.write(item.encrypted) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(item.initializationVector) + Encode.int16(item.curveType, buffer) + writeCoordinateComponent(buffer, Points.getX(item.R)) + writeCoordinateComponent(buffer, Points.getY(item.R)) + buffer.put(item.encrypted) + buffer.put(item.mac) + } + + 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(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 { @@ -187,15 +197,14 @@ class CryptoBox : Streamable { return this } - fun build(): CryptoBox { - return CryptoBox(this) - } + fun build() = CryptoBox(this) } companion object { private val LOG = LoggerFactory.getLogger(CryptoBox::class.java) - @JvmStatic fun read(stream: InputStream, length: Int): CryptoBox { + @JvmStatic + fun read(stream: InputStream, length: Int): CryptoBox { val counter = AccessCounter() return Builder() .IV(Decode.bytes(stream, 16, counter)) diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GenericPayload.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GenericPayload.kt index 10f0448..1ccd680 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GenericPayload.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GenericPayload.kt @@ -16,6 +16,7 @@ package ch.dissem.bitmessage.entity.payload +import ch.dissem.bitmessage.entity.SignedStreamableWriter import ch.dissem.bitmessage.utils.Decode import java.io.InputStream import java.io.OutputStream @@ -30,14 +31,6 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr 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 @@ -52,9 +45,27 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr return result } - companion object { - @JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { - return GenericPayload(version, stream, Decode.bytes(`is`, length)) + override fun writer(): SignedStreamableWriter = Writer(this) + + private class Writer( + private val item: GenericPayload + ) : SignedStreamableWriter { + + override fun write(out: OutputStream) { + out.write(item.data) } + + override fun write(buffer: ByteBuffer) { + buffer.put(item.data) + } + + override fun writeBytesToSign(out: OutputStream) = Unit // nothing to do + + } + + companion object { + @JvmStatic + fun read(version: Long, stream: Long, `is`: InputStream, length: Int) = + GenericPayload(version, stream, Decode.bytes(`is`, length)) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GetPubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GetPubkey.kt index fc6375f..893c36f 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GetPubkey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GetPubkey.kt @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.entity.payload import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.SignedStreamableWriter import ch.dissem.bitmessage.utils.Decode import java.io.InputStream import java.io.OutputStream @@ -47,16 +48,27 @@ class GetPubkey : ObjectPayload { this.ripeTag = ripeOrTag } - override fun write(out: OutputStream) { - out.write(ripeTag) - } + override fun writer(): SignedStreamableWriter = Writer(this) + + private class Writer( + private val item: GetPubkey + ) : SignedStreamableWriter { + + override fun write(out: OutputStream) { + out.write(item.ripeTag) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(item.ripeTag) + } + + override fun writeBytesToSign(out: OutputStream) = Unit // nothing to sign - override fun write(buffer: ByteBuffer) { - buffer.put(ripeTag) } companion object { - @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { + @JvmStatic + fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { return GetPubkey(version, stream, Decode.bytes(`is`, length)) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt index c2ebd8b..f8a4314 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt @@ -16,10 +16,8 @@ package ch.dissem.bitmessage.entity.payload -import ch.dissem.bitmessage.entity.Encrypted -import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.* 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 @@ -51,10 +49,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder { 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) { @@ -73,14 +67,6 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder { 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 @@ -89,14 +75,35 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder { return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) } - override fun hashCode(): Int { - return stream.toInt() + override fun hashCode() = stream.toInt() + + override fun writer(): SignedStreamableWriter = Writer(this) + + private class Writer( + private val item: Msg + ) : SignedStreamableWriter { + + val encryptedDataWriter = item.encrypted?.writer() + + override fun write(out: OutputStream) { + encryptedDataWriter?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") + } + + override fun write(buffer: ByteBuffer) { + encryptedDataWriter?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") + } + + override fun writeBytesToSign(out: OutputStream) { + item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available") + } + } companion object { val ACK_LENGTH = 32 - @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg { + @JvmStatic + fun read(`in`: InputStream, stream: Long, length: Int): Msg { return Msg(stream, CryptoBox.read(`in`, length)) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt index 5650652..69170ca 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt @@ -17,13 +17,14 @@ package ch.dissem.bitmessage.entity.payload import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.SignedStreamable 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 class ObjectPayload protected constructor(val version: Long) : SignedStreamable { abstract val type: ObjectType? @@ -31,10 +32,6 @@ abstract class ObjectPayload protected constructor(val version: Long) : Streamab 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 diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Pubkey.kt index 87e1355..5087bfa 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Pubkey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Pubkey.kt @@ -18,6 +18,8 @@ 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.entity.EncryptedStreamableWriter +import ch.dissem.bitmessage.entity.SignedStreamableWriter import ch.dissem.bitmessage.utils.Singleton.cryptography import java.io.OutputStream import java.nio.ByteBuffer @@ -42,13 +44,7 @@ abstract class Pubkey protected constructor(version: Long) : ObjectPayload(versi open val extraBytes: Long = NETWORK_EXTRA_BYTES - open fun writeUnencrypted(out: OutputStream) { - write(out) - } - - open fun writeUnencrypted(buffer: ByteBuffer) { - write(buffer) - } + abstract override fun writer(): EncryptedStreamableWriter /** * Bits 0 through 29 are yet undefined diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt index c1c7253..be50b78 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt @@ -16,6 +16,7 @@ package ch.dissem.bitmessage.entity.payload +import ch.dissem.bitmessage.entity.EncryptedStreamableWriter import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Encode import java.io.InputStream @@ -25,21 +26,47 @@ 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) { +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 writer(): EncryptedStreamableWriter = Writer(this) + + protected open class Writer( + private val item: V2Pubkey + ) : EncryptedStreamableWriter { + + override fun write(out: OutputStream) { + Encode.int32(item.behaviorBitfield, out) + out.write(item.signingKey, 1, 64) + out.write(item.encryptionKey, 1, 64) + } + + override fun write(buffer: ByteBuffer) { + Encode.int32(item.behaviorBitfield, buffer) + buffer.put(item.signingKey, 1, 64) + buffer.put(item.encryptionKey, 1, 64) + } + + override fun writeBytesToSign(out: OutputStream) { + // Nothing to do + } + + override fun writeUnencrypted(out: OutputStream) { + write(out) + } + + override fun writeUnencrypted(buffer: ByteBuffer) { + write(buffer) + } - override fun write(buffer: ByteBuffer) { - Encode.int32(behaviorBitfield, buffer) - buffer.put(signingKey, 1, 64) - buffer.put(encryptionKey, 1, 64) } class Builder { diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt index 53faed8..3149fa7 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt @@ -16,6 +16,9 @@ package ch.dissem.bitmessage.entity.payload +import ch.dissem.bitmessage.entity.EncryptedStreamableWriter +import ch.dissem.bitmessage.entity.SignedStreamableWriter +import ch.dissem.bitmessage.entity.StreamableWriter import ch.dissem.bitmessage.utils.Decode import ch.dissem.bitmessage.utils.Encode import java.io.InputStream @@ -34,32 +37,8 @@ class V3Pubkey protected constructor( 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 @@ -75,6 +54,37 @@ class V3Pubkey protected constructor( return Objects.hash(nonceTrialsPerByte, extraBytes) } + override fun writer(): EncryptedStreamableWriter = Writer(this) + + protected open class Writer( + private val item: V3Pubkey + ) : V2Pubkey.Writer(item) { + + override fun write(out: OutputStream) { + writeBytesToSign(out) + Encode.varBytes( + item.signature ?: throw IllegalStateException("signature not available"), + out + ) + } + + override fun write(buffer: ByteBuffer) { + super.write(buffer) + Encode.varInt(item.nonceTrialsPerByte, buffer) + Encode.varInt(item.extraBytes, buffer) + Encode.varBytes( + item.signature ?: throw IllegalStateException("signature not available"), + buffer + ) + } + + override fun writeBytesToSign(out: OutputStream) { + super.write(out) + Encode.varInt(item.nonceTrialsPerByte, out) + Encode.varInt(item.extraBytes, out) + } + } + class Builder { private var streamNumber: Long = 0 private var behaviorBitfield: Int = 0 diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt index e270754..6d5b7cf 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt @@ -18,7 +18,7 @@ package ch.dissem.bitmessage.entity.payload import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.Plaintext - +import ch.dissem.bitmessage.entity.SignedStreamableWriter import java.io.InputStream import java.io.OutputStream import java.nio.ByteBuffer @@ -38,21 +38,28 @@ open class V4Broadcast : Broadcast { throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version) } + override fun writer(): SignedStreamableWriter = Writer(this) - override fun writeBytesToSign(out: OutputStream) { - plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") - } + protected open class Writer( + private val item: V4Broadcast + ) : SignedStreamableWriter { - override fun write(out: OutputStream) { - encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted") - } + override fun writeBytesToSign(out: OutputStream) { + item.plaintext?.writer(false)?.write(out) ?: throw IllegalStateException("no plaintext data available") + } - override fun write(buffer: ByteBuffer) { - encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted") + override fun write(out: OutputStream) { + item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("broadcast not encrypted") + } + + override fun write(buffer: ByteBuffer) { + item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted") + } } companion object { - @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { + @JvmStatic + fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt index 1fc1196..a3e744a 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt @@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.entity.EncryptedStreamableWriter import ch.dissem.bitmessage.exception.DecryptionFailedException import ch.dissem.bitmessage.utils.Decode import java.io.InputStream @@ -63,29 +64,6 @@ class V4Pubkey : Pubkey, Encrypted { 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") @@ -126,8 +104,40 @@ class V4Pubkey : Pubkey, Encrypted { return result } + override fun writer(): EncryptedStreamableWriter = Writer(this) + + private class Writer( + val item: V4Pubkey + ) : EncryptedStreamableWriter { + + override fun write(out: OutputStream) { + out.write(item.tag) + item.encrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun write(buffer: ByteBuffer) { + buffer.put(item.tag) + item.encrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeUnencrypted(out: OutputStream) { + item.decrypted?.writer()?.write(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeUnencrypted(buffer: ByteBuffer) { + item.decrypted?.writer()?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeBytesToSign(out: OutputStream) { + out.write(item.tag) + item.decrypted?.writer()?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + } + companion object { - @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { + @JvmStatic + fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { if (encrypted) return V4Pubkey(stream, Decode.bytes(`in`, 32), diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt index 68b7454..4ca18d5 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt @@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.SignedStreamableWriter import ch.dissem.bitmessage.utils.Decode import java.io.InputStream @@ -40,18 +41,27 @@ class V5Broadcast : V4Broadcast { this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag") } - override fun writeBytesToSign(out: OutputStream) { - out.write(tag) - super.writeBytesToSign(out) - } + override fun writer(): SignedStreamableWriter = Writer(this) + + private class Writer( + private val item: V5Broadcast + ) : V4Broadcast.Writer(item) { + + override fun writeBytesToSign(out: OutputStream) { + out.write(item.tag) + super.writeBytesToSign(out) + } + + override fun write(out: OutputStream) { + out.write(item.tag) + super.write(out) + } - override fun write(out: OutputStream) { - out.write(tag) - super.write(out) } companion object { - @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { + @JvmStatic + fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32)) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt index 144211a..d766739 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.entity.valueobject import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.StreamableWriter import ch.dissem.bitmessage.utils.Strings import java.io.OutputStream import java.nio.ByteBuffer @@ -39,20 +40,29 @@ data class InventoryVector constructor( 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) } + override fun writer(): StreamableWriter = Writer(this) + + private class Writer( + private val item: InventoryVector + ) : StreamableWriter { + + override fun write(out: OutputStream) { + out.write(item.hash) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(item.hash) + } + + } + companion object { - @JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? { + @JvmStatic + fun fromHash(hash: ByteArray?): InventoryVector? { return InventoryVector( hash ?: return null ) diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt index 829bbfa..eb13a64 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.entity.valueobject import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.StreamableWriter import ch.dissem.bitmessage.entity.Version import ch.dissem.bitmessage.utils.Encode import ch.dissem.bitmessage.utils.UnixTime @@ -77,9 +78,7 @@ data class NetworkAddress( fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false - fun toInetAddress(): InetAddress { - return InetAddress.getByAddress(IPv6) - } + fun toInetAddress() = InetAddress.getByAddress(IPv6) override fun equals(other: Any?): Boolean { if (this === other) return true @@ -98,32 +97,40 @@ data class NetworkAddress( return "[" + toInetAddress() + "]:" + port } - override fun write(out: OutputStream) { - write(out, false) - } + fun writer(light: Boolean): StreamableWriter = Writer( + item = this, + light = light + ) - fun write(out: OutputStream, light: Boolean) { - if (!light) { - Encode.int64(time, out) - Encode.int32(stream, out) + override fun writer(): StreamableWriter = Writer( + item = this + ) + + private class Writer( + private val item: NetworkAddress, + private val light: Boolean = false + ) : StreamableWriter { + + override fun write(out: OutputStream) { + if (!light) { + Encode.int64(item.time, out) + Encode.int32(item.stream, out) + } + Encode.int64(item.services, out) + out.write(item.IPv6) + Encode.int16(item.port, 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) + override fun write(buffer: ByteBuffer) { + if (!light) { + Encode.int64(item.time, buffer) + Encode.int32(item.stream, buffer) + } + Encode.int64(item.services, buffer) + buffer.put(item.IPv6) + Encode.int16(item.port, buffer) } - Encode.int64(services, buffer) - buffer.put(IPv6) - Encode.int16(port, buffer) + } class Builder { @@ -194,6 +201,7 @@ data class NetworkAddress( } companion object { - @JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0) + @JvmField + val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt index c7f0b54..5b88e02 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt @@ -19,6 +19,7 @@ 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.StreamableWriter import ch.dissem.bitmessage.entity.payload.Pubkey import ch.dissem.bitmessage.exception.ApplicationException import ch.dissem.bitmessage.factory.Factory @@ -66,7 +67,52 @@ data class PrivateKey( builder.nonceTrialsPerByte, builder.extraBytes, *builder.features) ) - private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) { + override fun equals(other: Any?) = other is PrivateKey + && Arrays.equals(privateEncryptionKey, other.privateEncryptionKey) + && Arrays.equals(privateSigningKey, other.privateSigningKey) + && pubkey == other.pubkey + + override fun hashCode() = pubkey.hashCode() + + override fun writer(): StreamableWriter = Writer(this) + + private class Writer( + private val item: PrivateKey + ) : StreamableWriter { + + override fun write(out: OutputStream) { + Encode.varInt(item.pubkey.version, out) + Encode.varInt(item.pubkey.stream, out) + val baos = ByteArrayOutputStream() + item.pubkey.writer().writeUnencrypted(baos) + Encode.varInt(baos.size(), out) + out.write(baos.toByteArray()) + Encode.varBytes(item.privateSigningKey, out) + Encode.varBytes(item.privateEncryptionKey, out) + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(item.pubkey.version, buffer) + Encode.varInt(item.pubkey.stream, buffer) + try { + val baos = ByteArrayOutputStream() + item.pubkey.writer().writeUnencrypted(baos) + Encode.varBytes(baos.toByteArray(), buffer) + } catch (e: IOException) { + throw ApplicationException(e) + } + + Encode.varBytes(item.privateSigningKey, buffer) + Encode.varBytes(item.privateEncryptionKey, buffer) + } + + } + + 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 @@ -129,44 +175,12 @@ data class PrivateKey( } } - override fun write(out: OutputStream) { - Encode.varInt(pubkey.version, out) - Encode.varInt(pubkey.stream, out) - val baos = ByteArrayOutputStream() - pubkey.writeUnencrypted(baos) - Encode.varInt(baos.size(), out) - out.write(baos.toByteArray()) - Encode.varBytes(privateSigningKey, out) - Encode.varBytes(privateEncryptionKey, out) - } - - - override fun write(buffer: ByteBuffer) { - Encode.varInt(pubkey.version, buffer) - Encode.varInt(pubkey.stream, buffer) - try { - val baos = ByteArrayOutputStream() - pubkey.writeUnencrypted(baos) - Encode.varBytes(baos.toByteArray(), buffer) - } catch (e: IOException) { - throw ApplicationException(e) - } - - Encode.varBytes(privateSigningKey, buffer) - Encode.varBytes(privateEncryptionKey, buffer) - } - - override fun equals(other: Any?) = other is PrivateKey - && Arrays.equals(privateEncryptionKey, other.privateEncryptionKey) - && Arrays.equals(privateSigningKey, other.privateSigningKey) - && pubkey == other.pubkey - - override fun hashCode() = pubkey.hashCode() - companion object { - @JvmField val PRIVATE_KEY_SIZE = 32 + @JvmField + val PRIVATE_KEY_SIZE = 32 - @JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List { + @JvmStatic + fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List { val result = ArrayList(numberOfAddresses) val builder = Builder(version, stream, shorter).seed(passphrase) for (i in 0..numberOfAddresses - 1) { @@ -176,7 +190,8 @@ data class PrivateKey( return result } - @JvmStatic fun read(`is`: InputStream): PrivateKey { + @JvmStatic + fun read(`is`: InputStream): PrivateKey { val version = Decode.varInt(`is`).toInt() val stream = Decode.varInt(`is`) val len = Decode.varInt(`is`).toInt() diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt index c378394..2eaf6e0 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt @@ -45,25 +45,25 @@ data class Message constructor( override fun pack(): MPMap> { val result = MPMap>() - result.put(mp(""), mp(TYPE)) - result.put(mp("subject"), mp(subject)) - result.put(mp("body"), mp(body)) + result.put("".mp, TYPE.mp) + result.put("subject".mp, subject.mp) + result.put("body".mp, body.mp) if (!files.isEmpty()) { val items = MPArray>>() - result.put(mp("files"), items) + result.put("files".mp, items) for (file in files) { val item = MPMap>() - item.put(mp("name"), mp(file.name)) - item.put(mp("data"), mp(*file.data)) - item.put(mp("type"), mp(file.type)) - item.put(mp("disposition"), mp(file.disposition.name)) + item.put("name".mp, file.name.mp) + item.put("data".mp, file.data.mp) + item.put("type".mp, file.type.mp) + item.put("disposition".mp, file.disposition.name.mp) items.add(item) } } if (!parents.isEmpty()) { val items = MPArray() - result.put(mp("parents"), items) + result.put("parents".mp, items) for ((hash) in parents) { items.add(mp(*hash)) } @@ -139,26 +139,26 @@ data class Message constructor( override val type: String = TYPE override fun unpack(map: MPMap>): Message { - val subject = str(map[mp("subject")]) ?: "" - val body = str(map[mp("body")]) ?: "" + val subject = str(map["subject".mp]) ?: "" + val body = str(map["body".mp]) ?: "" val parents = LinkedList() val files = LinkedList() - val mpParents = map[mp("parents")] as? MPArray<*> + val mpParents = map["parents".mp] as? MPArray<*> for (parent in mpParents ?: emptyList>()) { parents.add(InventoryVector.fromHash( (parent as? MPBinary)?.value ?: continue ) ?: continue) } - val mpFiles = map[mp("files")] as? MPArray<*> + val mpFiles = map["files".mp] as? MPArray<*> for (item in mpFiles ?: emptyList()) { if (item is MPMap<*, *>) { val b = Attachment.Builder() - b.name(str(item[mp("name")])!!) + b.name(str(item["name".mp])!!) b.data( - bin(item[mp("data")] ?: continue) ?: continue + bin(item["data".mp] ?: continue) ?: continue ) - b.type(str(item[mp("type")])!!) - val disposition = str(item[mp("disposition")]) + b.type(str(item["type".mp])!!) + val disposition = str(item["disposition".mp]) if ("inline" == disposition) { b.inline() } else if ("attachment" == disposition) { diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt index d26d38a..43750bb 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt @@ -35,9 +35,9 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte override fun pack(): MPMap> { val result = MPMap>() - result.put(mp(""), mp(TYPE)) - result.put(mp("msgId"), mp(*msgId.hash)) - result.put(mp("vote"), mp(vote)) + result.put("".mp, TYPE.mp) + result.put("msgId".mp, msgId.hash.mp) + result.put("vote".mp, vote.mp) return result } @@ -77,13 +77,14 @@ data class Vote constructor(val msgId: InventoryVector, val vote: String) : Exte get() = TYPE override fun unpack(map: MPMap>): Vote { - val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId") - val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given") + val msgId = InventoryVector.fromHash((map["msgId".mp] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId") + val vote = str(map["vote".mp]) ?: throw IllegalArgumentException("no vote given") return Vote(msgId, vote) } } companion object { - @JvmField val TYPE = "vote" + @JvmField + val TYPE = "vote" } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt b/core/src/main/kotlin/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt index d8833c7..61e3c15 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt @@ -54,9 +54,8 @@ object ExtendedEncodingFactory { fun unzip(zippedData: ByteArray): ExtendedEncoding? { try { InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper -> - val reader = Reader.getInstance() @Suppress("UNCHECKED_CAST") - val map = reader.read(unzipper) as MPMap> + val map = Reader.read(unzipper) as MPMap> val messageType = map[KEY_MESSAGE_TYPE] if (messageType == null) { LOG.error("Missing message type") diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistry.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistry.kt index 922370b..fff183e 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistry.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistry.kt @@ -31,4 +31,13 @@ interface NodeRegistry { fun getKnownAddresses(limit: Int, vararg streams: Long): List fun offerAddresses(nodes: List) + + fun update(node: NetworkAddress) + + fun remove(node: NetworkAddress) + + /** + * Remove stale nodes + */ + fun cleanup() } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/utils/DebugUtils.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/DebugUtils.kt index acbbabf..03eee08 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/utils/DebugUtils.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/utils/DebugUtils.kt @@ -29,7 +29,7 @@ object DebugUtils { try { val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv") f.createNewFile() - objectMessage.write(FileOutputStream(f)) + objectMessage.writer().write(FileOutputStream(f)) } catch (e: IOException) { LOG.debug(e.message, e) } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/utils/Encode.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Encode.kt index 1b96935..d69af86 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/utils/Encode.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Encode.kt @@ -26,22 +26,27 @@ import java.nio.ByteBuffer * https://bitmessage.org/wiki/Protocol_specification#Common_structures */ object Encode { - @JvmStatic fun varIntList(values: LongArray, stream: OutputStream) { + @JvmStatic + fun varIntList(values: LongArray, stream: OutputStream) { varInt(values.size, stream) for (value in values) { varInt(value, stream) } } - @JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) { + @JvmStatic + fun varIntList(values: LongArray, buffer: ByteBuffer) { varInt(values.size, buffer) for (value in values) { varInt(value, buffer) } } - @JvmStatic fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer) - @JvmStatic fun varInt(value: Long, buffer: ByteBuffer) { + @JvmStatic + fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer) + + @JvmStatic + fun varInt(value: Long, buffer: ByteBuffer) { 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. @@ -63,16 +68,24 @@ object Encode { } } - @JvmStatic fun varInt(value: Int) = varInt(value.toLong()) - @JvmStatic fun varInt(value: Long): ByteArray { + @JvmStatic + fun varInt(value: Int) = varInt(value.toLong()) + + @JvmStatic + fun varInt(value: Long): ByteArray { val buffer = ByteBuffer.allocate(9) varInt(value, buffer) buffer.flip() return Bytes.truncate(buffer.array(), buffer.limit()) } - @JvmStatic @JvmOverloads fun varInt(value: Int, stream: OutputStream, counter: AccessCounter? = null) = varInt(value.toLong(), stream, counter) - @JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + @JvmStatic + @JvmOverloads + fun varInt(value: Int, stream: OutputStream, counter: AccessCounter? = null) = varInt(value.toLong(), stream, counter) + + @JvmStatic + @JvmOverloads + fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { val buffer = ByteBuffer.allocate(9) varInt(value, buffer) buffer.flip() @@ -80,46 +93,76 @@ object Encode { AccessCounter.inc(counter, buffer.limit()) } - @JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter) - @JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) { + @JvmStatic + @JvmOverloads + fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter) + + @JvmStatic + @JvmOverloads + fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) { stream.write(value) AccessCounter.inc(counter) } - @JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) - @JvmStatic @JvmOverloads fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) - @JvmStatic @JvmOverloads fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) { + @JvmStatic + @JvmOverloads + fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) + + @JvmStatic + @JvmOverloads + fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) + + @JvmStatic + @JvmOverloads + fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) { stream.write(ByteBuffer.allocate(2).putShort(value).array()) AccessCounter.inc(counter, 2) } - @JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer) - @JvmStatic fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer) - @JvmStatic fun int16(value: Short, buffer: ByteBuffer) { + @JvmStatic + fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer) + + @JvmStatic + fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer) + + @JvmStatic + fun int16(value: Short, buffer: ByteBuffer) { buffer.putShort(value) } - @JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter) - @JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) { + @JvmStatic + @JvmOverloads + fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter) + + @JvmStatic + @JvmOverloads + fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) { stream.write(ByteBuffer.allocate(4).putInt(value).array()) AccessCounter.inc(counter, 4) } - @JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer) - @JvmStatic fun int32(value: Int, buffer: ByteBuffer) { + @JvmStatic + fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer) + + @JvmStatic + fun int32(value: Int, buffer: ByteBuffer) { buffer.putInt(value) } - @JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + @JvmStatic + @JvmOverloads + fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { stream.write(ByteBuffer.allocate(8).putLong(value).array()) AccessCounter.inc(counter, 8) } - @JvmStatic fun int64(value: Long, buffer: ByteBuffer) { + @JvmStatic + fun int64(value: Long, buffer: ByteBuffer) { buffer.putLong(value) } - @JvmStatic fun varString(value: String, out: OutputStream) { + @JvmStatic + fun varString(value: String, out: OutputStream) { val bytes = value.toByteArray(charset("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. @@ -128,7 +171,8 @@ object Encode { out.write(bytes) } - @JvmStatic fun varString(value: String, buffer: ByteBuffer) { + @JvmStatic + fun varString(value: String, buffer: ByteBuffer) { val bytes = value.toByteArray() // 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. @@ -137,12 +181,14 @@ object Encode { buffer.put(bytes) } - @JvmStatic fun varBytes(data: ByteArray, out: OutputStream) { + @JvmStatic + fun varBytes(data: ByteArray, out: OutputStream) { varInt(data.size.toLong(), out) out.write(data) } - @JvmStatic fun varBytes(data: ByteArray, buffer: ByteBuffer) { + @JvmStatic + fun varBytes(data: ByteArray, buffer: ByteBuffer) { varInt(data.size.toLong(), buffer) buffer.put(data) } @@ -152,9 +198,10 @@ object Encode { * @param streamable the object to be serialized * @return an array of bytes representing the given streamable object. */ - @JvmStatic fun bytes(streamable: Streamable): ByteArray { + @JvmStatic + fun bytes(streamable: Streamable): ByteArray { val stream = ByteArrayOutputStream() - streamable.write(stream) + streamable.writer().write(stream) return stream.toByteArray() } @@ -163,9 +210,10 @@ object Encode { * @param padding the result will be padded such that its length is a multiple of *padding* * @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding. */ - @JvmStatic fun bytes(streamable: Streamable, padding: Int): ByteArray { + @JvmStatic + fun bytes(streamable: Streamable, padding: Int): ByteArray { val stream = ByteArrayOutputStream() - streamable.write(stream) + streamable.writer().write(stream) val offset = padding - stream.size() % padding val length = stream.size() + offset val result = ByteArray(length) diff --git a/core/src/test/kotlin/ch/dissem/bitmessage/entity/SerializationTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/entity/SerializationTest.kt index 22b2d4f..10845e7 100644 --- a/core/src/test/kotlin/ch/dissem/bitmessage/entity/SerializationTest.kt +++ b/core/src/test/kotlin/ch/dissem/bitmessage/entity/SerializationTest.kt @@ -86,7 +86,7 @@ class SerializationTest : TestBase() { .signature(ByteArray(0)) .build() val out = ByteArrayOutputStream() - expected.write(out) + expected.writer().write(out) val `in` = ByteArrayInputStream(out.toByteArray()) val actual = Plaintext.read(MSG, `in`) @@ -111,7 +111,7 @@ class SerializationTest : TestBase() { .signature(ByteArray(0)) .build() val out = ByteArrayOutputStream() - expected.write(out) + expected.writer().write(out) val `in` = ByteArrayInputStream(out.toByteArray()) val actual = Plaintext.read(MSG, `in`) @@ -136,7 +136,7 @@ class SerializationTest : TestBase() { assertNotNull(ackMessage1) val out = ByteArrayOutputStream() - expected.write(out) + expected.writer().write(out) val `in` = ByteArrayInputStream(out.toByteArray()) val actual = Plaintext.read(MSG, `in`) @@ -159,7 +159,7 @@ class SerializationTest : TestBase() { val inv = Inv(ivs) val before = NetworkMessage(inv) val out = ByteArrayOutputStream() - before.write(out) + before.writer().write(out) val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray())) assertNotNull(after) @@ -173,7 +173,7 @@ class SerializationTest : TestBase() { val objectMessage = Factory.getObjectMessage(version, `in`, data.size) val out = ByteArrayOutputStream() assertNotNull(objectMessage) - objectMessage!!.write(out) + objectMessage!!.writer().write(out) assertArrayEquals(data, out.toByteArray()) assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName) } diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 40ee063..a2e005a 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -24,6 +24,7 @@ import ch.dissem.bitmessage.ports.NodeRegistry; import ch.dissem.bitmessage.repository.*; import ch.dissem.bitmessage.wif.WifExporter; import ch.dissem.bitmessage.wif.WifImporter; +import org.jetbrains.annotations.NotNull; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; @@ -68,12 +69,28 @@ public class Main { if (options.localPort != null) { ctxBuilder.nodeRegistry(new NodeRegistry() { @Override - public void clear() { + public void cleanup() { // NO OP } @Override - public List getKnownAddresses(int limit, long... streams) { + public void remove(@NotNull NetworkAddress node) { + // NO OP + } + + @Override + public void update(@NotNull NetworkAddress node) { + // NO OP + } + + @Override + public void clear() { + // NO OP + } + + @NotNull + @Override + public List getKnownAddresses(int limit, @NotNull long... streams) { return Arrays.stream(streams) .mapToObj(s -> new NetworkAddress.Builder() .ipv4(127, 0, 0, 1) @@ -83,7 +100,7 @@ public class Main { } @Override - public void offerAddresses(List nodes) { + public void offerAddresses(@NotNull List nodes) { LOG.info("Local node registry ignored offered addresses: " + nodes); } }); diff --git a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java index 09f7a85..c6ce5ba 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java +++ b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java @@ -18,6 +18,7 @@ package ch.dissem.bitmessage; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.ports.NodeRegistry; +import org.jetbrains.annotations.NotNull; import java.util.LinkedList; import java.util.List; @@ -44,13 +45,29 @@ class TestNodeRegistry implements NodeRegistry { // NO OP } + @NotNull @Override - public List getKnownAddresses(int limit, long... streams) { + public List getKnownAddresses(int limit, @NotNull long... streams) { return nodes; } @Override - public void offerAddresses(List nodes) { + public void offerAddresses(@NotNull List nodes) { + // Ignore + } + + @Override + public void update(@NotNull NetworkAddress node) { + // Ignore + } + + @Override + public void remove(@NotNull NetworkAddress node) { + // Ignore + } + + @Override + public void cleanup() { // Ignore } } diff --git a/exports/build.gradle b/exports/build.gradle index 718aabb..e4dbd29 100644 --- a/exports/build.gradle +++ b/exports/build.gradle @@ -15,9 +15,9 @@ dependencies { compile 'org.slf4j:slf4j-api' compile 'com.beust:klaxon' - testCompile 'junit:junit:4.12' - testCompile 'org.hamcrest:hamcrest-library:1.3' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + testCompile 'junit:junit' + testCompile 'org.hamcrest:hamcrest-library' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt b/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt index e429881..5796ea3 100644 --- a/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt +++ b/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt @@ -39,7 +39,7 @@ object ContactExport { "subscribed" to it.isSubscribed, "pubkey" to it.pubkey?.let { val out = ByteArrayOutputStream() - it.writeUnencrypted(out) + it.writer().writeUnencrypted(out) Base64.encodeToString(out.toByteArray()) }, "privateKey" to if (includePrivateKey) { diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt index d5a2349..4d67cb1 100644 --- a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt @@ -19,6 +19,7 @@ package ch.dissem.bitmessage.extensions import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.CustomMessage import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.StreamableWriter import ch.dissem.bitmessage.entity.payload.CryptoBox import ch.dissem.bitmessage.entity.payload.Pubkey import ch.dissem.bitmessage.exception.DecryptionFailedException @@ -73,7 +74,7 @@ class CryptoCustomMessage : CustomMessage { Encode.varInt(privateKey.pubkey.extraBytes, out) } - data?.write(out) ?: throw IllegalStateException("no unencrypted data available") + data?.writer()?.write(out) ?: throw IllegalStateException("no unencrypted data available") Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out) container = CryptoBox(out.toByteArray(), publicKey) } @@ -109,9 +110,17 @@ class CryptoCustomMessage : CustomMessage { return data!! } - override fun write(out: OutputStream) { - Encode.varString(COMMAND, out) - container?.write(out) ?: throw IllegalStateException("not encrypted yet") + override fun writer(): StreamableWriter = Writer(this) + + private class Writer( + private val item: CryptoCustomMessage<*> + ) : CustomMessage.Writer(item) { + + override fun write(out: OutputStream) { + Encode.varString(COMMAND, out) + item.container?.writer()?.write(out) ?: throw IllegalStateException("not encrypted yet") + } + } interface Reader { @@ -136,9 +145,11 @@ class CryptoCustomMessage : CustomMessage { } companion object { - @JvmField val COMMAND = "ENCRYPTED" + @JvmField + val COMMAND = "ENCRYPTED" - @JvmStatic fun read(data: CustomMessage, dataReader: Reader): CryptoCustomMessage { + @JvmStatic + fun read(data: CustomMessage, dataReader: Reader): CryptoCustomMessage { val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size) return CryptoCustomMessage(cryptoBox, dataReader) } diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt index e2c2ba8..bb368f7 100644 --- a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt @@ -18,6 +18,7 @@ package ch.dissem.bitmessage.extensions.pow import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.StreamableWriter import ch.dissem.bitmessage.extensions.CryptoCustomMessage import ch.dissem.bitmessage.utils.Decode.bytes import ch.dissem.bitmessage.utils.Decode.varBytes @@ -33,16 +34,24 @@ import java.util.* */ data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable { - override fun write(out: OutputStream) { - out.write(initialHash) - Encode.varString(request.name, out) - Encode.varBytes(data, out) - } + override fun writer(): StreamableWriter = Writer(this) + + private class Writer( + private val item: ProofOfWorkRequest + ) : StreamableWriter { + + override fun write(out: OutputStream) { + out.write(item.initialHash) + Encode.varString(item.request.name, out) + Encode.varBytes(item.data, out) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(item.initialHash) + Encode.varString(item.request.name, buffer) + Encode.varBytes(item.data, buffer) + } - override fun write(buffer: ByteBuffer) { - buffer.put(initialHash) - Encode.varString(request.name, buffer) - Encode.varBytes(data, buffer) } class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader { diff --git a/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt b/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt index f722fa0..90dfa54 100644 --- a/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt +++ b/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt @@ -43,7 +43,7 @@ class CryptoCustomMessageTest : TestBase() { messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)) val out = ByteArrayOutputStream() - messageBefore.write(out) + messageBefore.writer().write(out) val `in` = ByteArrayInputStream(out.toByteArray()) val customMessage = CustomMessage.read(`in`, out.size()) @@ -71,7 +71,7 @@ class CryptoCustomMessageTest : TestBase() { val out = ByteArrayOutputStream() - messageBefore.write(out) + messageBefore.writer().write(out) val `in` = ByteArrayInputStream(out.toByteArray()) val customMessage = CustomMessage.read(`in`, out.size()) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c9f10ca3aae0effbc09843516439732a7cad6cff..9e23bfb8e908c975ebf1d23f5edb6fbf8fc4152d 100644 GIT binary patch delta 19315 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 channel.configureBlocking(true) val headerBuffer = ByteBuffer.allocate(HEADER_SIZE) - val payloadBuffer = NetworkMessage(request).writeHeaderAndGetPayloadBuffer(headerBuffer) + val payloadBuffer = NetworkMessage(request).writer().writeHeaderAndGetPayloadBuffer(headerBuffer) headerBuffer.flip() while (headerBuffer.hasRemaining()) { channel.write(headerBuffer) diff --git a/networking/src/test/kotlin/ch/dissem/bitmessage/networking/TestNodeRegistry.kt b/networking/src/test/kotlin/ch/dissem/bitmessage/networking/TestNodeRegistry.kt index a454c91..89ab3eb 100644 --- a/networking/src/test/kotlin/ch/dissem/bitmessage/networking/TestNodeRegistry.kt +++ b/networking/src/test/kotlin/ch/dissem/bitmessage/networking/TestNodeRegistry.kt @@ -38,4 +38,16 @@ internal class TestNodeRegistry(vararg nodes: NetworkAddress) : NodeRegistry { override fun offerAddresses(nodes: List) { // Ignore } + + override fun update(node: NetworkAddress) { + // Ignore + } + + override fun remove(node: NetworkAddress) { + // Ignore + } + + override fun cleanup() { + // Ignore + } } diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt index 29a0f87..477aa5a 100644 --- a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt @@ -175,7 +175,7 @@ class JdbcAddressRepository(config: JdbcConfig) : JdbcHelper(config), AddressRep private fun writePubkey(ps: PreparedStatement, parameterIndex: Int, data: Pubkey?) { if (data != null) { val out = ByteArrayOutputStream() - data.writeUnencrypted(out) + data.writer().writeUnencrypted(out) ps.setBytes(parameterIndex, out.toByteArray()) } else { ps.setBytes(parameterIndex, null) diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt index bbc7b70..9970574 100644 --- a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt @@ -30,7 +30,7 @@ abstract class JdbcHelper protected constructor(@JvmField protected val config: ps.setBytes(parameterIndex, null) } else { val os = ByteArrayOutputStream() - data.write(os) + data.writer().write(os) ps.setBytes(parameterIndex, os.toByteArray()) } } diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt index ccdbe75..6aff7f0 100644 --- a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt @@ -19,9 +19,8 @@ package ch.dissem.bitmessage.repository import ch.dissem.bitmessage.entity.valueobject.NetworkAddress import ch.dissem.bitmessage.ports.NodeRegistry import ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes +import ch.dissem.bitmessage.utils.* import ch.dissem.bitmessage.utils.Collections -import ch.dissem.bitmessage.utils.SqlStrings -import ch.dissem.bitmessage.utils.Strings import ch.dissem.bitmessage.utils.UnixTime.DAY import ch.dissem.bitmessage.utils.UnixTime.MINUTE import ch.dissem.bitmessage.utils.UnixTime.now @@ -155,7 +154,18 @@ class JdbcNodeRegistry(config: JdbcConfig) : JdbcHelper(config), NodeRegistry { ps.setBytes(2, node.IPv6) ps.setInt(3, node.port) ps.setLong(4, node.services) - ps.setLong(5, node.time) + ps.setLong(5, + if (node.time > UnixTime.now) { + // This might be an attack, let's not use those nodes with priority + UnixTime.now - 7 * UnixTime.DAY + } else if (node.time == 0L) { + // Those just don't have a time set + // let's give them slightly higher priority than the possible attack ones + UnixTime.now - 6 * UnixTime.DAY + } else { + node.time + } + ) ps.executeUpdate() } } @@ -164,13 +174,20 @@ class JdbcNodeRegistry(config: JdbcConfig) : JdbcHelper(config), NodeRegistry { } } - private fun update(node: NetworkAddress) { + override fun update(node: NetworkAddress) { try { + val time = if (node.time > UnixTime.now) { + // This might be an attack, let's not use those nodes with priority + UnixTime.now - 7 * UnixTime.DAY + } else { + node.time + } + config.getConnection().use { connection -> connection.prepareStatement( "UPDATE Node SET services=?, time=? WHERE stream=? AND address=? AND port=?").use { ps -> ps.setLong(1, node.services) - ps.setLong(2, node.time) + ps.setLong(2, max(node.time, time)) ps.setLong(3, node.stream) ps.setBytes(4, node.IPv6) ps.setInt(5, node.port) @@ -182,6 +199,36 @@ class JdbcNodeRegistry(config: JdbcConfig) : JdbcHelper(config), NodeRegistry { } } + override fun remove(node: NetworkAddress) { + try { + config.getConnection().use { connection -> + connection.prepareStatement( + "DELETE FROM Node WHERE stream=? AND address=? AND port=?").use { ps -> + ps.setLong(1, node.stream) + ps.setBytes(2, node.IPv6) + ps.setInt(3, node.port) + ps.executeUpdate() + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + + override fun cleanup() { + try { + config.getConnection().use { connection -> + connection.prepareStatement( + "DELETE FROM Node WHERE time + ps.setLong(1, UnixTime.now - 8 * DAY) + ps.executeUpdate() + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + companion object { private val LOG = LoggerFactory.getLogger(JdbcNodeRegistry::class.java) }