Merge branch 'feature/exports' into develop
This commit is contained in:
		@@ -1,5 +1,5 @@
 | 
			
		||||
buildscript {
 | 
			
		||||
    ext.kotlin_version = '1.1.3'
 | 
			
		||||
    ext.kotlin_version = '1.1.4-2'
 | 
			
		||||
    repositories {
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
    }
 | 
			
		||||
@@ -28,6 +28,7 @@ subprojects {
 | 
			
		||||
    repositories {
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
 | 
			
		||||
        jcenter()
 | 
			
		||||
    }
 | 
			
		||||
    dependencies {
 | 
			
		||||
        compile "org.jetbrains.kotlin:kotlin-stdlib-jre7"
 | 
			
		||||
@@ -142,6 +143,7 @@ subprojects {
 | 
			
		||||
            dependency 'com.madgag.spongycastle:prov:1.56.0.0'
 | 
			
		||||
            dependency 'org.apache.commons:commons-lang3:3.6'
 | 
			
		||||
            dependency 'org.flywaydb:flyway-core:4.2.0'
 | 
			
		||||
            dependency 'com.beust:klaxon:0.31'
 | 
			
		||||
 | 
			
		||||
            dependency 'args4j:args4j:2.33'
 | 
			
		||||
            dependency 'org.ini4j:ini4j:0.5.4'
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED
 | 
			
		||||
import ch.dissem.bitmessage.entity.payload.*
 | 
			
		||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
 | 
			
		||||
import ch.dissem.bitmessage.exception.DecryptionFailedException
 | 
			
		||||
import ch.dissem.bitmessage.ports.AlreadyStoredException
 | 
			
		||||
import ch.dissem.bitmessage.ports.Labeler
 | 
			
		||||
import ch.dissem.bitmessage.ports.NetworkHandler
 | 
			
		||||
import ch.dissem.bitmessage.utils.Strings.hex
 | 
			
		||||
@@ -65,7 +66,7 @@ open class DefaultMessageListener(
 | 
			
		||||
 | 
			
		||||
    protected fun receive(objectMessage: ObjectMessage, getPubkey: GetPubkey) {
 | 
			
		||||
        val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag)
 | 
			
		||||
        if (identity != null && identity.privateKey != null && !identity.isChan) {
 | 
			
		||||
        if (identity?.privateKey != null && !identity.isChan) {
 | 
			
		||||
            LOG.info("Got pubkey request for identity " + identity)
 | 
			
		||||
            // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days
 | 
			
		||||
            ctx.sendPubkey(identity, objectMessage.stream)
 | 
			
		||||
@@ -90,7 +91,6 @@ open class DefaultMessageListener(
 | 
			
		||||
            }
 | 
			
		||||
        } catch (_: DecryptionFailedException) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updatePubkey(address: BitmessageAddress, pubkey: Pubkey) {
 | 
			
		||||
@@ -157,14 +157,18 @@ open class DefaultMessageListener(
 | 
			
		||||
 | 
			
		||||
        msg.inventoryVector = iv
 | 
			
		||||
        labeler.setLabels(msg)
 | 
			
		||||
        ctx.messageRepository.save(msg)
 | 
			
		||||
        listener.receive(msg)
 | 
			
		||||
        try {
 | 
			
		||||
            ctx.messageRepository.save(msg)
 | 
			
		||||
            listener.receive(msg)
 | 
			
		||||
 | 
			
		||||
        if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) {
 | 
			
		||||
            msg.ackMessage?.let {
 | 
			
		||||
                ctx.inventory.storeObject(it)
 | 
			
		||||
                ctx.networkHandler.offer(it.inventoryVector)
 | 
			
		||||
            } ?: LOG.debug("ack message expected")
 | 
			
		||||
            if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) {
 | 
			
		||||
                msg.ackMessage?.let {
 | 
			
		||||
                    ctx.inventory.storeObject(it)
 | 
			
		||||
                    ctx.networkHandler.offer(it.inventoryVector)
 | 
			
		||||
                } ?: LOG.debug("ack message expected")
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: AlreadyStoredException) {
 | 
			
		||||
            LOG.trace("Message was already received before.", e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -229,7 +229,8 @@ class Plaintext private constructor(
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    constructor(builder: Builder) : this(
 | 
			
		||||
        type = builder.type,
 | 
			
		||||
        // Calling prepare() here is somewhat ugly, but also a foolproof way to make sure the builder is properly initialized
 | 
			
		||||
        type = builder.prepare().type,
 | 
			
		||||
        from = builder.from ?: throw IllegalStateException("sender identity not set"),
 | 
			
		||||
        to = builder.to,
 | 
			
		||||
        encodingCode = builder.encoding,
 | 
			
		||||
@@ -438,7 +439,7 @@ class Plaintext private constructor(
 | 
			
		||||
        if (this === other) return true
 | 
			
		||||
        if (other !is Plaintext) return false
 | 
			
		||||
        return encoding == other.encoding &&
 | 
			
		||||
            from == other.from &&
 | 
			
		||||
            from.address == other.from.address &&
 | 
			
		||||
            Arrays.equals(message, other.message) &&
 | 
			
		||||
            ackMessage == other.ackMessage &&
 | 
			
		||||
            Arrays.equals(to?.ripe, other.to?.ripe) &&
 | 
			
		||||
@@ -532,6 +533,7 @@ class Plaintext private constructor(
 | 
			
		||||
        private var nonceTrialsPerByte: Long = 0
 | 
			
		||||
        private var extraBytes: Long = 0
 | 
			
		||||
        private var destinationRipe: ByteArray? = null
 | 
			
		||||
        private var preventAck: Boolean = false
 | 
			
		||||
        internal var encoding: Long = 0
 | 
			
		||||
        internal var message = ByteArray(0)
 | 
			
		||||
        internal var ackData: ByteArray? = null
 | 
			
		||||
@@ -611,6 +613,12 @@ class Plaintext private constructor(
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @JvmOverloads
 | 
			
		||||
        fun preventAck(preventAck: Boolean = true): Builder {
 | 
			
		||||
            this.preventAck = preventAck
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun encoding(encoding: Encoding): Builder {
 | 
			
		||||
            this.encoding = encoding.code
 | 
			
		||||
            return this
 | 
			
		||||
@@ -655,7 +663,7 @@ class Plaintext private constructor(
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun signature(signature: ByteArray): Builder {
 | 
			
		||||
        fun signature(signature: ByteArray?): Builder {
 | 
			
		||||
            this.signature = signature
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
@@ -700,7 +708,7 @@ class Plaintext private constructor(
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun build(): Plaintext {
 | 
			
		||||
        internal fun prepare(): Builder {
 | 
			
		||||
            if (from == null) {
 | 
			
		||||
                from = BitmessageAddress(Factory.createPubkey(
 | 
			
		||||
                    addressVersion,
 | 
			
		||||
@@ -715,12 +723,19 @@ class Plaintext private constructor(
 | 
			
		||||
            if (to == null && type != Type.BROADCAST && destinationRipe != null) {
 | 
			
		||||
                to = BitmessageAddress(0, 0, destinationRipe!!)
 | 
			
		||||
            }
 | 
			
		||||
            if (type == MSG && ackMessage == null && ackData == null) {
 | 
			
		||||
            if (preventAck) {
 | 
			
		||||
                ackData = null
 | 
			
		||||
                ackMessage = null
 | 
			
		||||
            } else if (type == MSG && ackMessage == null && ackData == null) {
 | 
			
		||||
                ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
 | 
			
		||||
            }
 | 
			
		||||
            if (ttl <= 0) {
 | 
			
		||||
                ttl = TTL.msg
 | 
			
		||||
            }
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun build(): Plaintext {
 | 
			
		||||
            return Plaintext(this)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,8 @@ data class Label(
 | 
			
		||||
    /**
 | 
			
		||||
     * RGBA representation for the color.
 | 
			
		||||
     */
 | 
			
		||||
    var color: Int = 0
 | 
			
		||||
    var color: Int = 0,
 | 
			
		||||
    var ord: Int = 1000
 | 
			
		||||
) : Serializable {
 | 
			
		||||
 | 
			
		||||
    var id: Any? = null
 | 
			
		||||
 
 | 
			
		||||
@@ -34,48 +34,37 @@ import java.util.*
 | 
			
		||||
 * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
 | 
			
		||||
 * [Pubkey] object.
 | 
			
		||||
 */
 | 
			
		||||
class PrivateKey : Streamable {
 | 
			
		||||
 | 
			
		||||
    val privateSigningKey: ByteArray
 | 
			
		||||
    val privateEncryptionKey: ByteArray
 | 
			
		||||
data class PrivateKey(
 | 
			
		||||
    val privateSigningKey: ByteArray,
 | 
			
		||||
    val privateEncryptionKey: ByteArray,
 | 
			
		||||
 | 
			
		||||
    val pubkey: Pubkey
 | 
			
		||||
) : Streamable {
 | 
			
		||||
 | 
			
		||||
    constructor(shorter: Boolean, stream: Long, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature) {
 | 
			
		||||
        var privSK: ByteArray
 | 
			
		||||
        var pubSK: ByteArray
 | 
			
		||||
        var privEK: ByteArray
 | 
			
		||||
        var pubEK: ByteArray
 | 
			
		||||
        var ripe: ByteArray
 | 
			
		||||
        do {
 | 
			
		||||
            privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE)
 | 
			
		||||
            privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE)
 | 
			
		||||
            pubSK = cryptography().createPublicKey(privSK)
 | 
			
		||||
            pubEK = cryptography().createPublicKey(privEK)
 | 
			
		||||
            ripe = Pubkey.getRipe(pubSK, pubEK)
 | 
			
		||||
        } while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0)
 | 
			
		||||
        this.privateSigningKey = privSK
 | 
			
		||||
        this.privateEncryptionKey = privEK
 | 
			
		||||
        this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
 | 
			
		||||
            nonceTrialsPerByte, extraBytes, *features)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, pubkey: Pubkey) {
 | 
			
		||||
        this.privateSigningKey = privateSigningKey
 | 
			
		||||
        this.privateEncryptionKey = privateEncryptionKey
 | 
			
		||||
        this.pubkey = pubkey
 | 
			
		||||
    }
 | 
			
		||||
    constructor(
 | 
			
		||||
        shorter: Boolean,
 | 
			
		||||
        stream: Long,
 | 
			
		||||
        nonceTrialsPerByte: Long, extraBytes: Long,
 | 
			
		||||
        vararg features: Pubkey.Feature
 | 
			
		||||
    ) : this(
 | 
			
		||||
        Builder(version = Pubkey.LATEST_VERSION, stream = stream, shorter = shorter)
 | 
			
		||||
            .random()
 | 
			
		||||
            .nonceTrialsPerByte(nonceTrialsPerByte)
 | 
			
		||||
            .extraBytes(extraBytes)
 | 
			
		||||
            .features(features)
 | 
			
		||||
            .generate())
 | 
			
		||||
 | 
			
		||||
    constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase)
 | 
			
		||||
 | 
			
		||||
    constructor(version: Long, stream: Long, passphrase: String) : this(Builder(version, stream, false).seed(passphrase).generate())
 | 
			
		||||
    constructor(version: Long, stream: Long, passphrase: String) : this(
 | 
			
		||||
        Builder(version, stream, false).seed(passphrase).generate()
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private constructor(builder: Builder) {
 | 
			
		||||
        this.privateSigningKey = builder.privSK!!
 | 
			
		||||
        this.privateEncryptionKey = builder.privEK!!
 | 
			
		||||
        this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!,
 | 
			
		||||
            InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES)
 | 
			
		||||
    }
 | 
			
		||||
    private constructor(builder: Builder) : this(
 | 
			
		||||
        builder.privSK!!, builder.privEK!!,
 | 
			
		||||
        Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!,
 | 
			
		||||
            builder.nonceTrialsPerByte, builder.extraBytes, *builder.features)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) {
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +76,30 @@ class PrivateKey : Streamable {
 | 
			
		||||
        internal var pubSK: ByteArray? = null
 | 
			
		||||
        internal var pubEK: ByteArray? = null
 | 
			
		||||
 | 
			
		||||
        internal var nonceTrialsPerByte = InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE
 | 
			
		||||
        internal var extraBytes = InternalContext.NETWORK_EXTRA_BYTES
 | 
			
		||||
        internal var features: Array<out Pubkey.Feature> = emptyArray()
 | 
			
		||||
 | 
			
		||||
        internal fun random(): Builder {
 | 
			
		||||
            seed = cryptography().randomBytes(1024)
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
 | 
			
		||||
            this.nonceTrialsPerByte = nonceTrialsPerByte
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun extraBytes(extraBytes: Long): Builder {
 | 
			
		||||
            this.extraBytes = extraBytes
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun features(features: Array<out Pubkey.Feature>): Builder {
 | 
			
		||||
            this.features = features
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal fun seed(passphrase: String): Builder {
 | 
			
		||||
            try {
 | 
			
		||||
                seed = passphrase.toByteArray(charset("UTF-8"))
 | 
			
		||||
@@ -143,6 +156,13 @@ class PrivateKey : Streamable {
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -198,10 +198,10 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        protected val LOG = LoggerFactory.getLogger(Cryptography::class.java)
 | 
			
		||||
        protected val LOG = LoggerFactory.getLogger(Cryptography::class.java)!!
 | 
			
		||||
        private val RANDOM = SecureRandom()
 | 
			
		||||
        private val TWO = BigInteger.valueOf(2)
 | 
			
		||||
        private val TWO_POW_64 = TWO.pow(64)
 | 
			
		||||
        private val TWO_POW_16 = TWO.pow(16)
 | 
			
		||||
        private val TWO_POW_64 = TWO.pow(64)!!
 | 
			
		||||
        private val TWO_POW_16 = TWO.pow(16)!!
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,8 @@ abstract class AbstractMessageRepository : MessageRepository, InternalContext.Co
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getAllMessages() = find("1=1")
 | 
			
		||||
 | 
			
		||||
    override fun getMessage(id: Any): Plaintext {
 | 
			
		||||
        if (id is Long) {
 | 
			
		||||
            return single(find("id=" + id)) ?: throw IllegalArgumentException("There  is no message with id $id")
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2017 Christian Basler
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.dissem.bitmessage.ports
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Should be thrown if a received and decrypted message can't be stored because it has already been received and stored.
 | 
			
		||||
 * (So it's not announced again to the client.)
 | 
			
		||||
 */
 | 
			
		||||
class AlreadyStoredException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
 | 
			
		||||
@@ -28,8 +28,12 @@ interface MessageRepository {
 | 
			
		||||
 | 
			
		||||
    fun getLabels(vararg types: Label.Type): List<Label>
 | 
			
		||||
 | 
			
		||||
    fun save(label: Label)
 | 
			
		||||
 | 
			
		||||
    fun countUnread(label: Label?): Int
 | 
			
		||||
 | 
			
		||||
    fun getAllMessages(): List<Plaintext>
 | 
			
		||||
 | 
			
		||||
    fun getMessage(id: Any): Plaintext
 | 
			
		||||
 | 
			
		||||
    fun getMessage(iv: InventoryVector): Plaintext?
 | 
			
		||||
 
 | 
			
		||||
@@ -88,21 +88,22 @@ object Base58 {
 | 
			
		||||
        val input58 = ByteArray(input.length)
 | 
			
		||||
        // Transform the String to a base58 byte sequence
 | 
			
		||||
        for (i in 0..input.length - 1) {
 | 
			
		||||
            val c = input[i]
 | 
			
		||||
            val c = input[i].toInt()
 | 
			
		||||
 | 
			
		||||
            var digit58 = -1
 | 
			
		||||
            if (c.toInt() < 128) {
 | 
			
		||||
                digit58 = INDEXES[c.toInt()]
 | 
			
		||||
            val digit58 = if (c < 128) {
 | 
			
		||||
                INDEXES[c]
 | 
			
		||||
            } else {
 | 
			
		||||
                -1
 | 
			
		||||
            }
 | 
			
		||||
            if (digit58 < 0) {
 | 
			
		||||
                throw AddressFormatException("Illegal character $c at $i")
 | 
			
		||||
                throw AddressFormatException("Illegal character ${input[i]} at $i")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            input58[i] = digit58.toByte()
 | 
			
		||||
        }
 | 
			
		||||
        // Count leading zeroes
 | 
			
		||||
        var zeroCount = 0
 | 
			
		||||
        while (zeroCount < input58.size && input58[zeroCount].toInt() == 0) {
 | 
			
		||||
        while (zeroCount < input58.size && input58[zeroCount] == 0.toByte()) {
 | 
			
		||||
            ++zeroCount
 | 
			
		||||
        }
 | 
			
		||||
        // The encoding
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										664
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/utils/Base64.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										664
									
								
								core/src/main/kotlin/ch/dissem/bitmessage/utils/Base64.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,664 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2010 The Android Open Source Project
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.dissem.bitmessage.utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utilities for encoding and decoding the Base64 representation of
 | 
			
		||||
 * binary data.  See RFCs <a
 | 
			
		||||
 * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
 | 
			
		||||
 * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
 | 
			
		||||
 */
 | 
			
		||||
object Base64 {
 | 
			
		||||
    /**
 | 
			
		||||
     * Encoder flag bit to omit the padding '=' characters at the end
 | 
			
		||||
     * of the output (if any).
 | 
			
		||||
     */
 | 
			
		||||
    const val NO_PADDING = 1
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encoder flag bit to omit all line terminators (i.e., the output
 | 
			
		||||
     * will be on one long line).
 | 
			
		||||
     */
 | 
			
		||||
    const val NO_WRAP = 2
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encoder flag bit to indicate lines should be terminated with a
 | 
			
		||||
     * CRLF pair instead of just an LF.  Has no effect if `NO_WRAP` is specified as well.
 | 
			
		||||
     */
 | 
			
		||||
    const val CRLF = 4
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encoder/decoder flag bit to indicate using the "URL and
 | 
			
		||||
     * filename safe" variant of Base64 (see RFC 3548 section 4) where
 | 
			
		||||
     * `-` and `_` are used in place of `+` and
 | 
			
		||||
     * `/`.
 | 
			
		||||
     */
 | 
			
		||||
    const val URL_SAFE = 8
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default values for encoder/decoder flags.
 | 
			
		||||
     */
 | 
			
		||||
    const val DEFAULT = NO_WRAP
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    //  --------------------------------------------------------
 | 
			
		||||
    //  decoding
 | 
			
		||||
    //  --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decode the Base64-encoded data in input and return the data in
 | 
			
		||||
     * a new byte array.
 | 
			
		||||
 | 
			
		||||
     *
 | 
			
		||||
     * The padding '=' characters at the end are considered optional, but
 | 
			
		||||
     * if any are present, there must be the correct number of them.
 | 
			
		||||
 | 
			
		||||
     * @param str    the input String to decode, which is converted to
 | 
			
		||||
     * *               bytes using the default charset
 | 
			
		||||
     * *
 | 
			
		||||
     * @param flags  controls certain features of the decoded output.
 | 
			
		||||
     * *               Pass `DEFAULT` to decode standard Base64.
 | 
			
		||||
     * *
 | 
			
		||||
     * *
 | 
			
		||||
     * @throws IllegalArgumentException if the input contains
 | 
			
		||||
     * * incorrect padding
 | 
			
		||||
     */
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun decode(str: String, flags: Int = DEFAULT): ByteArray {
 | 
			
		||||
        val input = str.toByteArray(Charsets.US_ASCII)
 | 
			
		||||
        return decode(input, 0, input.size, flags)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decode the Base64-encoded data in input and return the data in
 | 
			
		||||
     * a new byte array.
 | 
			
		||||
 | 
			
		||||
     *
 | 
			
		||||
     * The padding '=' characters at the end are considered optional, but
 | 
			
		||||
     * if any are present, there must be the correct number of them.
 | 
			
		||||
 | 
			
		||||
     * @param input  the data to decode
 | 
			
		||||
     * *
 | 
			
		||||
     * @param offset the position within the input array at which to start
 | 
			
		||||
     * *
 | 
			
		||||
     * @param len    the number of bytes of input to decode
 | 
			
		||||
     * *
 | 
			
		||||
     * @param flags  controls certain features of the decoded output.
 | 
			
		||||
     * *               Pass `DEFAULT` to decode standard Base64.
 | 
			
		||||
     * *
 | 
			
		||||
     * *
 | 
			
		||||
     * @throws IllegalArgumentException if the input contains
 | 
			
		||||
     * * incorrect padding
 | 
			
		||||
     */
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun decode(input: ByteArray, offset: Int, len: Int, flags: Int = DEFAULT): ByteArray {
 | 
			
		||||
        // Allocate space for the most data the input could represent.
 | 
			
		||||
        // (It could contain less if it contains whitespace, etc.)
 | 
			
		||||
        val decoder = Decoder(Options(flags), ByteArray(len * 3 / 4))
 | 
			
		||||
 | 
			
		||||
        if (!decoder.process(input, offset, len, true)) {
 | 
			
		||||
            throw IllegalArgumentException("bad base-64")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Maybe we got lucky and allocated exactly enough output space.
 | 
			
		||||
        if (decoder.op == decoder.output.size) {
 | 
			
		||||
            return decoder.output
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Need to shorten the array, so allocate a new one of the
 | 
			
		||||
        // right size and copy.
 | 
			
		||||
        val temp = ByteArray(decoder.op)
 | 
			
		||||
        System.arraycopy(decoder.output, 0, temp, 0, decoder.op)
 | 
			
		||||
        return temp
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //  --------------------------------------------------------
 | 
			
		||||
    //  encoding
 | 
			
		||||
    //  --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Base64-encode the given data and return a newly allocated
 | 
			
		||||
     * String with the result.
 | 
			
		||||
 | 
			
		||||
     * @param input  the data to encode
 | 
			
		||||
     * *
 | 
			
		||||
     * @param flags  controls certain features of the encoded output.
 | 
			
		||||
     * *               Passing `DEFAULT` results in output that
 | 
			
		||||
     * *               adheres to RFC 2045.
 | 
			
		||||
     */
 | 
			
		||||
    fun encodeToString(input: ByteArray, flags: Int = DEFAULT): String {
 | 
			
		||||
        return String(encode(input, 0, input.size, flags), Charsets.US_ASCII)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Base64-encode the given data and return a newly allocated
 | 
			
		||||
     * byte[] with the result.
 | 
			
		||||
 | 
			
		||||
     * @param input  the data to encode
 | 
			
		||||
     * *
 | 
			
		||||
     * @param offset the position within the input array at which to
 | 
			
		||||
     * *               start
 | 
			
		||||
     * *
 | 
			
		||||
     * @param len    the number of bytes of input to encode
 | 
			
		||||
     * *
 | 
			
		||||
     * @param flags  controls certain features of the encoded output.
 | 
			
		||||
     * *               Passing `DEFAULT` results in output that
 | 
			
		||||
     * *               adheres to RFC 2045.
 | 
			
		||||
     */
 | 
			
		||||
    fun encode(input: ByteArray, offset: Int, len: Int, flags: Int): ByteArray {
 | 
			
		||||
        // Compute the exact length of the array we will produce.
 | 
			
		||||
        var output_len = len / 3 * 4
 | 
			
		||||
 | 
			
		||||
        val options = Options(flags)
 | 
			
		||||
 | 
			
		||||
        // Account for the tail of the data and the padding bytes, if any.
 | 
			
		||||
        if (options.do_padding) {
 | 
			
		||||
            if (len % 3 > 0) {
 | 
			
		||||
                output_len += 4
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            when (len % 3) {
 | 
			
		||||
                0 -> {
 | 
			
		||||
                }
 | 
			
		||||
                1 -> output_len += 2
 | 
			
		||||
                2 -> output_len += 3
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Account for the newlines, if any.
 | 
			
		||||
        if (options.do_newline && len > 0) {
 | 
			
		||||
            output_len += ((len - 1) / (3 * Encoder.LINE_GROUPS) + 1) * if (options.do_cr) 2 else 1
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val encoder = Encoder(options, ByteArray(output_len))
 | 
			
		||||
        encoder.process(input, offset, len, true)
 | 
			
		||||
 | 
			
		||||
        assert(encoder.op == output_len)
 | 
			
		||||
 | 
			
		||||
        return encoder.output
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Options(flags: Int) {
 | 
			
		||||
    val do_padding = flags and Base64.NO_PADDING == 0
 | 
			
		||||
    val do_newline = flags and Base64.NO_WRAP == 0
 | 
			
		||||
    val do_cr = flags and Base64.CRLF != 0
 | 
			
		||||
    val url_safe = flags and Base64.URL_SAFE == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private abstract class Coder(val output: ByteArray) {
 | 
			
		||||
    var op = 0
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encode/decode another block of input data.  this.output is
 | 
			
		||||
     * provided by the caller, and must be big enough to hold all
 | 
			
		||||
     * the coded data.  On exit, this.opwill be set to the length
 | 
			
		||||
     * of the coded data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param finish true if this is the final call to process for
 | 
			
		||||
     *        this object.  Will finalize the coder state and
 | 
			
		||||
     *        include any final bytes in the output.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the input so far is good; false if some
 | 
			
		||||
     *         error has been detected in the input stream..
 | 
			
		||||
     */
 | 
			
		||||
    abstract fun process(input: ByteArray, offset: Int, len: Int, finish: Boolean): Boolean
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return the maximum number of bytes a call to process()
 | 
			
		||||
     *         could produce for the given number of input bytes.  This may
 | 
			
		||||
     *         be an overestimate.
 | 
			
		||||
     */
 | 
			
		||||
    abstract fun maxOutputSize(len: Int): Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class Decoder(options: Options, output: ByteArray) : Coder(output) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * States 0-3 are reading through the next input tuple.
 | 
			
		||||
     * State 4 is having read one '=' and expecting exactly
 | 
			
		||||
     * one more.
 | 
			
		||||
     * State 5 is expecting no more data or padding characters
 | 
			
		||||
     * in the input.
 | 
			
		||||
     * State 6 is the error state; an error has been detected
 | 
			
		||||
     * in the input and no future input can "fix" it.
 | 
			
		||||
     */
 | 
			
		||||
    private var state: Int = 0   // state number (0 to 6)
 | 
			
		||||
    private var value: Int = 0
 | 
			
		||||
 | 
			
		||||
    private val alphabet = if (options.url_safe) DECODE else DECODE_WEBSAFE
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return an overestimate for the number of bytes `len` bytes could decode to.
 | 
			
		||||
     */
 | 
			
		||||
    override fun maxOutputSize(len: Int): Int {
 | 
			
		||||
        return len * 3 / 4 + 10
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decode another block of input data.
 | 
			
		||||
 | 
			
		||||
     * @return true if the state machine is still healthy.  false if
 | 
			
		||||
     * *         bad base-64 data has been detected in the input stream.
 | 
			
		||||
     */
 | 
			
		||||
    override fun process(input: ByteArray, offset: Int, len: Int, finish: Boolean): Boolean {
 | 
			
		||||
        var end = len
 | 
			
		||||
        if (this.state == 6) return false
 | 
			
		||||
 | 
			
		||||
        var p = offset
 | 
			
		||||
        end += offset
 | 
			
		||||
 | 
			
		||||
        // Using local variables makes the decoder about 12%
 | 
			
		||||
        // faster than if we manipulate the member variables in
 | 
			
		||||
        // the loop.  (Even alphabet makes a measurable
 | 
			
		||||
        // difference, which is somewhat surprising to me since
 | 
			
		||||
        // the member variable is final.)
 | 
			
		||||
        var state = this.state
 | 
			
		||||
        var value = this.value
 | 
			
		||||
        var op = 0
 | 
			
		||||
        val output = this.output
 | 
			
		||||
        val alphabet = this.alphabet
 | 
			
		||||
 | 
			
		||||
        while (p < end) {
 | 
			
		||||
            // Try the fast path:  we're starting a new tuple and the
 | 
			
		||||
            // next four bytes of the input stream are all data
 | 
			
		||||
            // bytes.  This corresponds to going through states
 | 
			
		||||
            // 0-1-2-3-0.  We expect to use this method for most of
 | 
			
		||||
            // the data.
 | 
			
		||||
            //
 | 
			
		||||
            // If any of the next four bytes of input are non-data
 | 
			
		||||
            // (whitespace, etc.), value will end up negative.  (All
 | 
			
		||||
            // the non-data values in decode are small negative
 | 
			
		||||
            // numbers, so shifting any of them up and or'ing them
 | 
			
		||||
            // together will result in a value with its top bit set.)
 | 
			
		||||
            //
 | 
			
		||||
            // You can remove this whole block and the output should
 | 
			
		||||
            // be the same, just slower.
 | 
			
		||||
            if (state == 0) {
 | 
			
		||||
                fun nextVal(): Int {
 | 
			
		||||
                    value = alphabet[input[p].toInt() and 0xff] shl 18 or
 | 
			
		||||
                        (alphabet[input[p + 1].toInt() and 0xff] shl 12) or
 | 
			
		||||
                        (alphabet[input[p + 2].toInt() and 0xff] shl 6) or
 | 
			
		||||
                        alphabet[input[p + 3].toInt() and 0xff]
 | 
			
		||||
                    return value
 | 
			
		||||
                }
 | 
			
		||||
                while (p + 4 <= end && nextVal() >= 0) {
 | 
			
		||||
                    output[op + 2] = value.toByte()
 | 
			
		||||
                    output[op + 1] = (value shr 8).toByte()
 | 
			
		||||
                    output[op] = (value shr 16).toByte()
 | 
			
		||||
                    op += 3
 | 
			
		||||
                    p += 4
 | 
			
		||||
                }
 | 
			
		||||
                if (p >= end) break
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // The fast path isn't available -- either we've read a
 | 
			
		||||
            // partial tuple, or the next four input bytes aren't all
 | 
			
		||||
            // data, or whatever.  Fall back to the slower state
 | 
			
		||||
            // machine implementation.
 | 
			
		||||
 | 
			
		||||
            val d = alphabet[input[p++].toInt() and 0xff]
 | 
			
		||||
 | 
			
		||||
            when (state) {
 | 
			
		||||
                0 -> if (d >= 0) {
 | 
			
		||||
                    value = d
 | 
			
		||||
                    ++state
 | 
			
		||||
                } else if (d != SKIP) {
 | 
			
		||||
                    this.state = 6
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                1 -> if (d >= 0) {
 | 
			
		||||
                    value = value shl 6 or d
 | 
			
		||||
                    ++state
 | 
			
		||||
                } else if (d != SKIP) {
 | 
			
		||||
                    this.state = 6
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                2 -> if (d >= 0) {
 | 
			
		||||
                    value = value shl 6 or d
 | 
			
		||||
                    ++state
 | 
			
		||||
                } else if (d == EQUALS) {
 | 
			
		||||
                    // Emit the last (partial) output tuple;
 | 
			
		||||
                    // expect exactly one more padding character.
 | 
			
		||||
                    output[op++] = (value shr 4).toByte()
 | 
			
		||||
                    state = 4
 | 
			
		||||
                } else if (d != SKIP) {
 | 
			
		||||
                    this.state = 6
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                3 -> if (d >= 0) {
 | 
			
		||||
                    // Emit the output triple and return to state 0.
 | 
			
		||||
                    value = value shl 6 or d
 | 
			
		||||
                    output[op + 2] = value.toByte()
 | 
			
		||||
                    output[op + 1] = (value shr 8).toByte()
 | 
			
		||||
                    output[op] = (value shr 16).toByte()
 | 
			
		||||
                    op += 3
 | 
			
		||||
                    state = 0
 | 
			
		||||
                } else if (d == EQUALS) {
 | 
			
		||||
                    // Emit the last (partial) output tuple;
 | 
			
		||||
                    // expect no further data or padding characters.
 | 
			
		||||
                    output[op + 1] = (value shr 2).toByte()
 | 
			
		||||
                    output[op] = (value shr 10).toByte()
 | 
			
		||||
                    op += 2
 | 
			
		||||
                    state = 5
 | 
			
		||||
                } else if (d != SKIP) {
 | 
			
		||||
                    this.state = 6
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                4 -> if (d == EQUALS) {
 | 
			
		||||
                    ++state
 | 
			
		||||
                } else if (d != SKIP) {
 | 
			
		||||
                    this.state = 6
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                5 -> if (d != SKIP) {
 | 
			
		||||
                    this.state = 6
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!finish) {
 | 
			
		||||
            // We're out of input, but a future call could provide
 | 
			
		||||
            // more.
 | 
			
		||||
            this.state = state
 | 
			
		||||
            this.value = value
 | 
			
		||||
            this.op = op
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Done reading input.  Now figure out where we are left in
 | 
			
		||||
        // the state machine and finish up.
 | 
			
		||||
 | 
			
		||||
        when (state) {
 | 
			
		||||
            0 -> {
 | 
			
		||||
            }
 | 
			
		||||
            1 -> {
 | 
			
		||||
                // Read one extra input byte, which isn't enough to
 | 
			
		||||
                // make another output byte.  Illegal.
 | 
			
		||||
                this.state = 6
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
            2 ->
 | 
			
		||||
                // Read two extra input bytes, enough to emit 1 more
 | 
			
		||||
                // output byte.  Fine.
 | 
			
		||||
                output[op++] = (value shr 4).toByte()
 | 
			
		||||
            3 -> {
 | 
			
		||||
                // Read three extra input bytes, enough to emit 2 more
 | 
			
		||||
                // output bytes.  Fine.
 | 
			
		||||
                output[op++] = (value shr 10).toByte()
 | 
			
		||||
                output[op++] = (value shr 2).toByte()
 | 
			
		||||
            }
 | 
			
		||||
            4 -> {
 | 
			
		||||
                // Read one padding '=' when we expected 2.  Illegal.
 | 
			
		||||
                this.state = 6
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
            5 -> {
 | 
			
		||||
            }
 | 
			
		||||
        }// Output length is a multiple of three.  Fine.
 | 
			
		||||
        // Read all the padding '='s we expected and no more.
 | 
			
		||||
        // Fine.
 | 
			
		||||
 | 
			
		||||
        this.state = state
 | 
			
		||||
        this.op = op
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        /**
 | 
			
		||||
         * Lookup table for turning bytes into their position in the
 | 
			
		||||
         * Base64 alphabet.
 | 
			
		||||
         */
 | 
			
		||||
        private val DECODE = intArrayOf(
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
 | 
			
		||||
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
 | 
			
		||||
            -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
 | 
			
		||||
            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
 | 
			
		||||
            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Decode lookup table for the "web safe" variant (RFC 3548
 | 
			
		||||
         * sec. 4) where - and _ replace + and /.
 | 
			
		||||
         */
 | 
			
		||||
        private val DECODE_WEBSAFE = intArrayOf(
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
 | 
			
		||||
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
 | 
			
		||||
            -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
 | 
			
		||||
            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
 | 
			
		||||
            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
 | 
			
		||||
            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 | 
			
		||||
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        /** Non-data values in the DECODE arrays.  */
 | 
			
		||||
        private val SKIP = -1
 | 
			
		||||
        private val EQUALS = -2
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class Encoder(val options: Options, output: ByteArray) : Coder(output) {
 | 
			
		||||
 | 
			
		||||
    private val alphabet: ByteArray = if (options.url_safe) ENCODE else ENCODE_WEBSAFE
 | 
			
		||||
 | 
			
		||||
    private val tail = ByteArray(2)
 | 
			
		||||
    private var tailLen = 0
 | 
			
		||||
    private var count = if (options.do_newline) LINE_GROUPS else -1
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return an overestimate for the number of bytes `len` bytes could encode to.
 | 
			
		||||
     */
 | 
			
		||||
    override fun maxOutputSize(len: Int): Int {
 | 
			
		||||
        return len * 8 / 5 + 10
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun process(input: ByteArray, offset: Int, len: Int, finish: Boolean): Boolean {
 | 
			
		||||
        var end = len
 | 
			
		||||
        // Using local variables makes the encoder about 9% faster.
 | 
			
		||||
        val alphabet = this.alphabet
 | 
			
		||||
        val output = this.output
 | 
			
		||||
        var op = 0
 | 
			
		||||
        var count = this.count
 | 
			
		||||
 | 
			
		||||
        var p = offset
 | 
			
		||||
        end += offset
 | 
			
		||||
        var v = -1
 | 
			
		||||
 | 
			
		||||
        // First we need to concatenate the tail of the previous call
 | 
			
		||||
        // with any input bytes available now and see if we can empty
 | 
			
		||||
        // the tail.
 | 
			
		||||
 | 
			
		||||
        when (tailLen) {
 | 
			
		||||
            0 -> {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            1 -> {
 | 
			
		||||
                if (p + 2 <= end) {
 | 
			
		||||
                    // A 1-byte tail with at least 2 bytes of
 | 
			
		||||
                    // input available now.
 | 
			
		||||
                    v = tail[0].toInt() and 0xff shl 16 or
 | 
			
		||||
                        (input[p++].toInt() and 0xff shl 8) or
 | 
			
		||||
                        (input[p++].toInt() and 0xff)
 | 
			
		||||
                    tailLen = 0
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            2 -> if (p + 1 <= end) {
 | 
			
		||||
                // A 2-byte tail with at least 1 byte of input.
 | 
			
		||||
                v = tail[0].toInt() and 0xff shl 16 or
 | 
			
		||||
                    (tail[1].toInt() and 0xff shl 8) or
 | 
			
		||||
                    (input[p++].toInt() and 0xff)
 | 
			
		||||
                tailLen = 0
 | 
			
		||||
            }
 | 
			
		||||
        }// There was no tail.
 | 
			
		||||
 | 
			
		||||
        if (v != -1) {
 | 
			
		||||
            output[op++] = alphabet[v shr 18 and 0x3f]
 | 
			
		||||
            output[op++] = alphabet[v shr 12 and 0x3f]
 | 
			
		||||
            output[op++] = alphabet[v shr 6 and 0x3f]
 | 
			
		||||
            output[op++] = alphabet[v and 0x3f]
 | 
			
		||||
            if (--count == 0) {
 | 
			
		||||
                if (options.do_cr) output[op++] = CR
 | 
			
		||||
                output[op++] = NL
 | 
			
		||||
                count = LINE_GROUPS
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // At this point either there is no tail, or there are fewer
 | 
			
		||||
        // than 3 bytes of input available.
 | 
			
		||||
 | 
			
		||||
        // The main loop, turning 3 input bytes into 4 output bytes on
 | 
			
		||||
        // each iteration.
 | 
			
		||||
        while (p + 3 <= end) {
 | 
			
		||||
            v = input[p].toInt() and 0xff shl 16 or
 | 
			
		||||
                (input[p + 1].toInt() and 0xff shl 8) or
 | 
			
		||||
                (input[p + 2].toInt() and 0xff)
 | 
			
		||||
            output[op] = alphabet[v shr 18 and 0x3f]
 | 
			
		||||
            output[op + 1] = alphabet[v shr 12 and 0x3f]
 | 
			
		||||
            output[op + 2] = alphabet[v shr 6 and 0x3f]
 | 
			
		||||
            output[op + 3] = alphabet[v and 0x3f]
 | 
			
		||||
            p += 3
 | 
			
		||||
            op += 4
 | 
			
		||||
            if (--count == 0) {
 | 
			
		||||
                if (options.do_cr) output[op++] = CR
 | 
			
		||||
                output[op++] = NL
 | 
			
		||||
                count = LINE_GROUPS
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (finish) {
 | 
			
		||||
            // Finish up the tail of the input.  Note that we need to
 | 
			
		||||
            // consume any bytes in tail before any bytes
 | 
			
		||||
            // remaining in input; there should be at most two bytes
 | 
			
		||||
            // total.
 | 
			
		||||
 | 
			
		||||
            if (p - tailLen == end - 1) {
 | 
			
		||||
                var t = 0
 | 
			
		||||
                v = (if (tailLen > 0) tail[t++] else input[p++]).toInt() and 0xff shl 4
 | 
			
		||||
                tailLen -= t
 | 
			
		||||
                output[op++] = alphabet[v shr 6 and 0x3f]
 | 
			
		||||
                output[op++] = alphabet[v and 0x3f]
 | 
			
		||||
                if (options.do_padding) {
 | 
			
		||||
                    output[op++] = PAD
 | 
			
		||||
                    output[op++] = PAD
 | 
			
		||||
                }
 | 
			
		||||
                if (options.do_newline) {
 | 
			
		||||
                    if (options.do_cr) output[op++] = CR
 | 
			
		||||
                    output[op++] = NL
 | 
			
		||||
                }
 | 
			
		||||
            } else if (p - tailLen == end - 2) {
 | 
			
		||||
                var t = 0
 | 
			
		||||
                v = (if (tailLen > 1) tail[t++] else input[p++]).toInt() and 0xff shl 10 or ((if (tailLen > 0) tail[t++] else input[p++]).toInt() and 0xff shl 2)
 | 
			
		||||
                tailLen -= t
 | 
			
		||||
                output[op++] = alphabet[v shr 12 and 0x3f]
 | 
			
		||||
                output[op++] = alphabet[v shr 6 and 0x3f]
 | 
			
		||||
                output[op++] = alphabet[v and 0x3f]
 | 
			
		||||
                if (options.do_padding) {
 | 
			
		||||
                    output[op++] = PAD
 | 
			
		||||
                }
 | 
			
		||||
                if (options.do_newline) {
 | 
			
		||||
                    if (options.do_cr) output[op++] = CR
 | 
			
		||||
                    output[op++] = NL
 | 
			
		||||
                }
 | 
			
		||||
            } else if (options.do_newline && op > 0 && count != LINE_GROUPS) {
 | 
			
		||||
                if (options.do_cr) output[op++] = CR
 | 
			
		||||
                output[op++] = NL
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            assert(tailLen == 0)
 | 
			
		||||
            assert(p == end)
 | 
			
		||||
        } else {
 | 
			
		||||
            // Save the leftovers in tail to be consumed on the next
 | 
			
		||||
            // call to encodeInternal.
 | 
			
		||||
 | 
			
		||||
            if (p == end - 1) {
 | 
			
		||||
                tail[tailLen++] = input[p]
 | 
			
		||||
            } else if (p == end - 2) {
 | 
			
		||||
                tail[tailLen++] = input[p]
 | 
			
		||||
                tail[tailLen++] = input[p + 1]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.op = op
 | 
			
		||||
        this.count = count
 | 
			
		||||
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val PAD = '='.toByte()
 | 
			
		||||
        private const val CR = '\r'.toByte()
 | 
			
		||||
        private const val NL = '\n'.toByte()
 | 
			
		||||
        /**
 | 
			
		||||
         * Emit a new line every this many output tuples.  Corresponds to
 | 
			
		||||
         * a 76-character line length (the maximum allowable according to
 | 
			
		||||
         * [RFC 2045](http://www.ietf.org/rfc/rfc2045.txt)).
 | 
			
		||||
         */
 | 
			
		||||
        val LINE_GROUPS = 19
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Lookup table for turning Base64 alphabet positions (6 bits)
 | 
			
		||||
         * into output bytes.
 | 
			
		||||
         */
 | 
			
		||||
        private val ENCODE = charArrayOf(
 | 
			
		||||
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 | 
			
		||||
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 | 
			
		||||
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 | 
			
		||||
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 | 
			
		||||
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
 | 
			
		||||
        ).map { it.toByte() }.toByteArray()
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Lookup table for turning Base64 alphabet positions (6 bits)
 | 
			
		||||
         * into output bytes.
 | 
			
		||||
         */
 | 
			
		||||
        private val ENCODE_WEBSAFE = charArrayOf(
 | 
			
		||||
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 | 
			
		||||
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 | 
			
		||||
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 | 
			
		||||
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 | 
			
		||||
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
 | 
			
		||||
        ).map { it.toByte() }.toByteArray()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,15 +23,15 @@ object UnixTime {
 | 
			
		||||
    /**
 | 
			
		||||
     * Length of a minute in seconds, intended for use with [.now].
 | 
			
		||||
     */
 | 
			
		||||
    @JvmField val MINUTE = 60L
 | 
			
		||||
    const val MINUTE = 60L
 | 
			
		||||
    /**
 | 
			
		||||
     * Length of an hour in seconds, intended for use with [.now].
 | 
			
		||||
     */
 | 
			
		||||
    @JvmField val HOUR = 60L * MINUTE
 | 
			
		||||
    const val HOUR = 60L * MINUTE
 | 
			
		||||
    /**
 | 
			
		||||
     * Length of a day in seconds, intended for use with [.now].
 | 
			
		||||
     */
 | 
			
		||||
    @JvmField val DAY = 24L * HOUR
 | 
			
		||||
    const val DAY = 24L * HOUR
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return the time in second based Unix time ([System.currentTimeMillis]/1000)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
[stream 1]
 | 
			
		||||
 | 
			
		||||
dissem.ch:8444
 | 
			
		||||
bitmessage.dissem.ch:8444
 | 
			
		||||
bootstrap8080.bitmessage.org:8080
 | 
			
		||||
bootstrap8444.bitmessage.org:8444
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -218,7 +218,7 @@ class BitmessageContextTest {
 | 
			
		||||
        verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject(
 | 
			
		||||
            argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L))
 | 
			
		||||
        assertEquals(2, testPowRepo.added)
 | 
			
		||||
        verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG })
 | 
			
		||||
        verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
@@ -228,7 +228,7 @@ class BitmessageContextTest {
 | 
			
		||||
            "Subject", "Message")
 | 
			
		||||
        verify(testPowRepo, timeout(10000).atLeastOnce())
 | 
			
		||||
            .putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L))
 | 
			
		||||
        verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG })
 | 
			
		||||
        verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.MSG })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(expected = IllegalArgumentException::class)
 | 
			
		||||
@@ -245,7 +245,7 @@ class BitmessageContextTest {
 | 
			
		||||
        verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce())
 | 
			
		||||
            .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L))
 | 
			
		||||
        verify(testPowEngine).calculateNonce(any(), any(), any())
 | 
			
		||||
        verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST })
 | 
			
		||||
        verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat<Plaintext> { type == Type.BROADCAST })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(expected = IllegalArgumentException::class)
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ class DefaultMessageListenerTest : TestBase() {
 | 
			
		||||
 | 
			
		||||
        listener.receive(objectMessage)
 | 
			
		||||
 | 
			
		||||
        verify(ctx.messageRepository, atLeastOnce()).save(argThat { type == MSG })
 | 
			
		||||
        verify(ctx.messageRepository, atLeastOnce()).save(argThat<Plaintext> { type == MSG })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
@@ -131,6 +131,6 @@ class DefaultMessageListenerTest : TestBase() {
 | 
			
		||||
 | 
			
		||||
        listener.receive(objectMessage)
 | 
			
		||||
 | 
			
		||||
        verify(ctx.messageRepository, atLeastOnce()).save(argThat { type == BROADCAST })
 | 
			
		||||
        verify(ctx.messageRepository, atLeastOnce()).save(argThat<Plaintext> { type == BROADCAST })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2017 Christian Basler
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.dissem.bitmessage.utils
 | 
			
		||||
 | 
			
		||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
 | 
			
		||||
import org.hamcrest.Matchers.`is`
 | 
			
		||||
import org.junit.Assert.assertThat
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
class Base64Test {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `ensure data is encoded and decoded correctly`() {
 | 
			
		||||
        val cryptography = BouncyCryptography()
 | 
			
		||||
        for (i in 100..200) {
 | 
			
		||||
            val data = cryptography.randomBytes(i)
 | 
			
		||||
            val string = Base64.encodeToString(data)
 | 
			
		||||
            val decoded = Base64.decode(string)
 | 
			
		||||
            assertThat(decoded, `is`(data))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -36,6 +36,8 @@ class ConversationServiceTest : TestBase() {
 | 
			
		||||
    private val messageRepository = mock<MessageRepository>()
 | 
			
		||||
    private val conversationService = spy(ConversationService(messageRepository))
 | 
			
		||||
 | 
			
		||||
    private val conversation = conversation(alice, bob)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `ensure conversation is sorted properly`() {
 | 
			
		||||
        MockitoKotlin.registerInstanceCreator { UUID.randomUUID() }
 | 
			
		||||
@@ -46,8 +48,9 @@ class ConversationServiceTest : TestBase() {
 | 
			
		||||
        assertThat(actual, `is`(expected))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val conversation: List<Plaintext>
 | 
			
		||||
        get() {
 | 
			
		||||
    companion object {
 | 
			
		||||
        private var timer = 2
 | 
			
		||||
        fun conversation(alice: BitmessageAddress, bob: BitmessageAddress): List<Plaintext> {
 | 
			
		||||
            val result = LinkedList<Plaintext>()
 | 
			
		||||
 | 
			
		||||
            val older = plaintext(alice, bob,
 | 
			
		||||
@@ -99,19 +102,18 @@ class ConversationServiceTest : TestBase() {
 | 
			
		||||
            return result
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private var timer = 2
 | 
			
		||||
 | 
			
		||||
    private fun plaintext(from: BitmessageAddress, to: BitmessageAddress,
 | 
			
		||||
                          content: ExtendedEncoding, status: Plaintext.Status): Plaintext {
 | 
			
		||||
        val builder = Plaintext.Builder(MSG)
 | 
			
		||||
            .IV(TestUtils.randomInventoryVector())
 | 
			
		||||
            .from(from)
 | 
			
		||||
            .to(to)
 | 
			
		||||
            .message(content)
 | 
			
		||||
            .status(status)
 | 
			
		||||
        if (status !== Plaintext.Status.DRAFT && status !== Plaintext.Status.DOING_PROOF_OF_WORK) {
 | 
			
		||||
            builder.received(5L * ++timer - RANDOM.nextInt(10))
 | 
			
		||||
        fun plaintext(from: BitmessageAddress, to: BitmessageAddress,
 | 
			
		||||
                      content: ExtendedEncoding, status: Plaintext.Status): Plaintext {
 | 
			
		||||
            val builder = Plaintext.Builder(MSG)
 | 
			
		||||
                .IV(TestUtils.randomInventoryVector())
 | 
			
		||||
                .from(from)
 | 
			
		||||
                .to(to)
 | 
			
		||||
                .message(content)
 | 
			
		||||
                .status(status)
 | 
			
		||||
            if (status !== Plaintext.Status.DRAFT && status !== Plaintext.Status.DOING_PROOF_OF_WORK) {
 | 
			
		||||
                builder.received(5L * ++timer - RANDOM.nextInt(10))
 | 
			
		||||
            }
 | 
			
		||||
            return builder.build()
 | 
			
		||||
        }
 | 
			
		||||
        return builder.build()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,7 @@ import ch.dissem.bitmessage.entity.valueobject.PrivateKey
 | 
			
		||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
 | 
			
		||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine
 | 
			
		||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
 | 
			
		||||
import ch.dissem.bitmessage.utils.CallbackWaiter
 | 
			
		||||
import ch.dissem.bitmessage.utils.Singleton
 | 
			
		||||
import ch.dissem.bitmessage.utils.TestUtils
 | 
			
		||||
import ch.dissem.bitmessage.utils.UnixTime
 | 
			
		||||
import ch.dissem.bitmessage.utils.*
 | 
			
		||||
import ch.dissem.bitmessage.utils.UnixTime.DAY
 | 
			
		||||
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
 | 
			
		||||
import ch.dissem.bitmessage.utils.UnixTime.now
 | 
			
		||||
@@ -34,7 +31,6 @@ import org.hamcrest.CoreMatchers.`is`
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
import java.io.ByteArrayInputStream
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import javax.xml.bind.DatatypeConverter
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -63,12 +59,12 @@ class CryptographyTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun ensureDoubleHashYieldsSameResultAsHashOfHash() {
 | 
			
		||||
    fun `ensure double hash yields same result as hash of hash`() {
 | 
			
		||||
        assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(expected = IOException::class)
 | 
			
		||||
    fun ensureExceptionForInsufficientProofOfWork() {
 | 
			
		||||
    @Test(expected = InsufficientProofOfWorkException::class)
 | 
			
		||||
    fun `ensure exception for insufficient proof of work`() {
 | 
			
		||||
        val objectMessage = ObjectMessage.Builder()
 | 
			
		||||
            .nonce(ByteArray(8))
 | 
			
		||||
            .expiresTime(UnixTime.now + 28 * DAY)
 | 
			
		||||
@@ -79,7 +75,7 @@ class CryptographyTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testDoProofOfWork() {
 | 
			
		||||
    fun `ensure proof of work is calculated correctly`() {
 | 
			
		||||
        TestUtils.mockedInternalContext(
 | 
			
		||||
            cryptography = crypto,
 | 
			
		||||
            proofOfWorkEngine = MultiThreadedPOWEngine()
 | 
			
		||||
@@ -108,7 +104,7 @@ class CryptographyTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun ensureEncryptionAndDecryptionWorks() {
 | 
			
		||||
    fun `ensure encryption and decryption works`() {
 | 
			
		||||
        val data = crypto.randomBytes(100)
 | 
			
		||||
        val key_e = crypto.randomBytes(32)
 | 
			
		||||
        val iv = crypto.randomBytes(16)
 | 
			
		||||
@@ -118,7 +114,7 @@ class CryptographyTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test(expected = IllegalArgumentException::class)
 | 
			
		||||
    fun ensureDecryptionFailsWithInvalidCypherText() {
 | 
			
		||||
    fun `ensure decryption fails with invalid cypher text`() {
 | 
			
		||||
        val data = crypto.randomBytes(128)
 | 
			
		||||
        val key_e = crypto.randomBytes(32)
 | 
			
		||||
        val iv = crypto.randomBytes(16)
 | 
			
		||||
@@ -126,7 +122,7 @@ class CryptographyTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testMultiplication() {
 | 
			
		||||
    fun `ensure multiplication works correctly`() {
 | 
			
		||||
        val a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE)
 | 
			
		||||
        val A = crypto.createPublicKey(a)
 | 
			
		||||
 | 
			
		||||
@@ -137,7 +133,7 @@ class CryptographyTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun ensureSignatureIsValid() {
 | 
			
		||||
    fun `ensure signature is valid`() {
 | 
			
		||||
        val data = crypto.randomBytes(100)
 | 
			
		||||
        val privateKey = PrivateKey(false, 1, 1000, 1000)
 | 
			
		||||
        val signature = crypto.getSignature(data, privateKey)
 | 
			
		||||
@@ -145,7 +141,7 @@ class CryptographyTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun ensureSignatureIsInvalidForTemperedData() {
 | 
			
		||||
    fun `ensure signature is invalid for tempered data`() {
 | 
			
		||||
        val data = crypto.randomBytes(100)
 | 
			
		||||
        val privateKey = PrivateKey(false, 1, 1000, 1000)
 | 
			
		||||
        val signature = crypto.getSignature(data, privateKey)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								exports/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								exports/build.gradle
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
uploadArchives {
 | 
			
		||||
    repositories {
 | 
			
		||||
        mavenDeployer {
 | 
			
		||||
            pom.project {
 | 
			
		||||
                name 'Jabit Exports'
 | 
			
		||||
                artifactId = 'jabit-exports'
 | 
			
		||||
                description 'Import and export data to JSON files.'
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    compile project(':core')
 | 
			
		||||
    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 project(path: ':core', configuration: 'testArtifacts')
 | 
			
		||||
    testCompile project(':cryptography-bc')
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2017 Christian Basler
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.dissem.bitmessage.exports
 | 
			
		||||
 | 
			
		||||
import ch.dissem.bitmessage.entity.BitmessageAddress
 | 
			
		||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
 | 
			
		||||
import ch.dissem.bitmessage.factory.Factory
 | 
			
		||||
import ch.dissem.bitmessage.utils.Base64
 | 
			
		||||
import ch.dissem.bitmessage.utils.Encode
 | 
			
		||||
import com.beust.klaxon.*
 | 
			
		||||
import java.io.ByteArrayInputStream
 | 
			
		||||
import java.io.ByteArrayOutputStream
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Exports and imports contacts and identities
 | 
			
		||||
 */
 | 
			
		||||
object ContactExport {
 | 
			
		||||
    fun exportContacts(contacts: List<BitmessageAddress>, includePrivateKey: Boolean = false) = json {
 | 
			
		||||
        array(
 | 
			
		||||
            contacts.map {
 | 
			
		||||
                obj(
 | 
			
		||||
                    "alias" to it.alias,
 | 
			
		||||
                    "address" to it.address,
 | 
			
		||||
                    "chan" to it.isChan,
 | 
			
		||||
                    "subscribed" to it.isSubscribed,
 | 
			
		||||
                    "pubkey" to it.pubkey?.let {
 | 
			
		||||
                        val out = ByteArrayOutputStream()
 | 
			
		||||
                        it.writeUnencrypted(out)
 | 
			
		||||
                        Base64.encodeToString(out.toByteArray())
 | 
			
		||||
                    },
 | 
			
		||||
                    "privateKey" to if (includePrivateKey) {
 | 
			
		||||
                        it.privateKey?.let { Base64.encodeToString(Encode.bytes(it)) }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        null
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun importContacts(input: JsonArray<*>): List<BitmessageAddress> {
 | 
			
		||||
        return input.filterIsInstance(JsonObject::class.java).map { json ->
 | 
			
		||||
            fun JsonObject.bytes(fieldName: String) = string(fieldName)?.let { Base64.decode(it) }
 | 
			
		||||
            val privateKey = json.bytes("privateKey")?.let { PrivateKey.read(ByteArrayInputStream(it)) }
 | 
			
		||||
            if (privateKey != null) {
 | 
			
		||||
                BitmessageAddress(privateKey)
 | 
			
		||||
            } else {
 | 
			
		||||
                BitmessageAddress(json.string("address") ?: throw IllegalArgumentException("address expected"))
 | 
			
		||||
            }.apply {
 | 
			
		||||
                alias = json.string("alias")
 | 
			
		||||
                isChan = json.boolean("chan") ?: false
 | 
			
		||||
                isSubscribed = json.boolean("subscribed") ?: false
 | 
			
		||||
                pubkey = json.bytes("pubkey")?.let {
 | 
			
		||||
                    Factory.readPubkey(
 | 
			
		||||
                        version = version,
 | 
			
		||||
                        stream = stream,
 | 
			
		||||
                        `is` = ByteArrayInputStream(it),
 | 
			
		||||
                        length = it.size,
 | 
			
		||||
                        encrypted = false
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,105 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2017 Christian Basler
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.dissem.bitmessage.exports
 | 
			
		||||
 | 
			
		||||
import ch.dissem.bitmessage.entity.BitmessageAddress
 | 
			
		||||
import ch.dissem.bitmessage.entity.Plaintext
 | 
			
		||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
 | 
			
		||||
import ch.dissem.bitmessage.entity.valueobject.Label
 | 
			
		||||
import ch.dissem.bitmessage.utils.Base64
 | 
			
		||||
import ch.dissem.bitmessage.utils.Encode
 | 
			
		||||
import ch.dissem.bitmessage.utils.TTL
 | 
			
		||||
import com.beust.klaxon.*
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Exports and imports messages and labels
 | 
			
		||||
 */
 | 
			
		||||
object MessageExport {
 | 
			
		||||
    fun exportLabels(labels: List<Label>) = json {
 | 
			
		||||
        array(
 | 
			
		||||
            labels.map {
 | 
			
		||||
                obj(
 | 
			
		||||
                    "label" to it.toString(),
 | 
			
		||||
                    "type" to it.type?.name,
 | 
			
		||||
                    "color" to it.color
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun exportMessages(messages: List<Plaintext>) = json {
 | 
			
		||||
        array(messages.map {
 | 
			
		||||
            obj(
 | 
			
		||||
                "type" to it.type.name,
 | 
			
		||||
                "from" to it.from.address,
 | 
			
		||||
                "to" to it.to?.address,
 | 
			
		||||
                "subject" to it.subject,
 | 
			
		||||
                "body" to it.text,
 | 
			
		||||
 | 
			
		||||
                "conversationId" to it.conversationId.toString(),
 | 
			
		||||
                "msgId" to it.inventoryVector?.hash?.let { Base64.encodeToString(it) },
 | 
			
		||||
                "encoding" to it.encodingCode,
 | 
			
		||||
                "status" to it.status.name,
 | 
			
		||||
                "message" to Base64.encodeToString(it.message),
 | 
			
		||||
                "ackData" to it.ackData?.let { Base64.encodeToString(it) },
 | 
			
		||||
                "ackMessage" to it.ackMessage?.let { Base64.encodeToString(Encode.bytes(it)) },
 | 
			
		||||
                "signature" to it.signature?.let { Base64.encodeToString(it) },
 | 
			
		||||
                "sent" to it.sent,
 | 
			
		||||
                "received" to it.received,
 | 
			
		||||
                "ttl" to it.ttl,
 | 
			
		||||
                "labels" to array(it.labels.map { it.toString() })
 | 
			
		||||
            )
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun importMessages(input: JsonArray<*>, labels: Map<String, Label>): List<Plaintext> {
 | 
			
		||||
        return input.filterIsInstance(JsonObject::class.java).map { json ->
 | 
			
		||||
            fun JsonObject.bytes(fieldName: String) = string(fieldName)?.let { Base64.decode(it) }
 | 
			
		||||
            Plaintext.Builder(Plaintext.Type.valueOf(json.string("type") ?: "MSG"))
 | 
			
		||||
                .from(json.string("from")?.let { BitmessageAddress(it) } ?: throw IllegalArgumentException("'from' address expected"))
 | 
			
		||||
                .to(json.string("to")?.let { BitmessageAddress(it) })
 | 
			
		||||
                .conversation(json.string("conversationId")?.let { UUID.fromString(it) } ?: UUID.randomUUID())
 | 
			
		||||
                .IV(json.bytes("msgId")?.let { InventoryVector(it) })
 | 
			
		||||
                .encoding(json.long("encoding") ?: throw IllegalArgumentException("encoding expected"))
 | 
			
		||||
                .status(json.string("status")?.let { Plaintext.Status.valueOf(it) } ?: throw IllegalArgumentException("status expected"))
 | 
			
		||||
                .message(json.bytes("message") ?: throw IllegalArgumentException("message expected"))
 | 
			
		||||
                .ackData(json.bytes("ackData"))
 | 
			
		||||
                .ackMessage(json.bytes("ackMessage"))
 | 
			
		||||
                .signature(json.bytes("signature"))
 | 
			
		||||
                .sent(json.long("sent"))
 | 
			
		||||
                .received(json.long("received"))
 | 
			
		||||
                .ttl(json.long("ttl") ?: TTL.msg)
 | 
			
		||||
                .labels(
 | 
			
		||||
                    json.array<String>("labels")?.map { labels[it] }?.filterNotNull() ?: emptyList()
 | 
			
		||||
                )
 | 
			
		||||
                .build()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun importLabels(input: JsonArray<*>): List<Label> {
 | 
			
		||||
        return input.filterIsInstance(JsonObject::class.java).map { json ->
 | 
			
		||||
            Label(
 | 
			
		||||
                label = json.string("label") ?: throw IllegalArgumentException("label expected"),
 | 
			
		||||
                type = json.string("type")?.let { Label.Type.valueOf(it) },
 | 
			
		||||
                color = json.int("color") ?: 0
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createLabelMap(labels: List<Label>) = labels.associateBy { it.toString() }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,73 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2017 Christian Basler
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.dissem.bitmessage.exports
 | 
			
		||||
 | 
			
		||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
 | 
			
		||||
import ch.dissem.bitmessage.entity.BitmessageAddress
 | 
			
		||||
import ch.dissem.bitmessage.utils.TestUtils
 | 
			
		||||
import org.hamcrest.CoreMatchers.`is`
 | 
			
		||||
import org.hamcrest.CoreMatchers.nullValue
 | 
			
		||||
import org.junit.Assert.assertThat
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
class ContactExportTest {
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        TestUtils.mockedInternalContext(cryptography = BouncyCryptography())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `ensure contacts are exported`() {
 | 
			
		||||
        val alice = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj")
 | 
			
		||||
        alice.alias = "Alice"
 | 
			
		||||
        alice.isSubscribed = true
 | 
			
		||||
        val contacts = listOf(
 | 
			
		||||
            BitmessageAddress("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci"),
 | 
			
		||||
            TestUtils.loadContact(),
 | 
			
		||||
            alice
 | 
			
		||||
        )
 | 
			
		||||
        val export = ContactExport.exportContacts(contacts)
 | 
			
		||||
        print(export.toJsonString(true))
 | 
			
		||||
        assertThat(ContactExport.importContacts(export), `is`(contacts))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `ensure private keys are omitted by default`() {
 | 
			
		||||
        val contacts = listOf(
 | 
			
		||||
            BitmessageAddress.chan(1, "test")
 | 
			
		||||
        )
 | 
			
		||||
        val export = ContactExport.exportContacts(contacts)
 | 
			
		||||
        print(export.toJsonString(true))
 | 
			
		||||
        val import = ContactExport.importContacts(export)
 | 
			
		||||
        assertThat(import.size, `is`(1))
 | 
			
		||||
        assertThat(import[0].isChan, `is`(true))
 | 
			
		||||
        assertThat(import[0].privateKey, `is`(nullValue()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `ensure private keys are exported if flag is set`() {
 | 
			
		||||
        val contacts = listOf(
 | 
			
		||||
            BitmessageAddress.chan(1, "test")
 | 
			
		||||
        )
 | 
			
		||||
        val export = ContactExport.exportContacts(contacts, true)
 | 
			
		||||
        print(export.toJsonString(true))
 | 
			
		||||
        val import = ContactExport.importContacts(export)
 | 
			
		||||
        assertThat(import.size, `is`(1))
 | 
			
		||||
        assertThat(import[0].isChan, `is`(true))
 | 
			
		||||
        assertThat(import[0].privateKey, `is`(contacts[0].privateKey))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2017 Christian Basler
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package ch.dissem.bitmessage.exports
 | 
			
		||||
 | 
			
		||||
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography
 | 
			
		||||
import ch.dissem.bitmessage.entity.BitmessageAddress
 | 
			
		||||
import ch.dissem.bitmessage.entity.Plaintext
 | 
			
		||||
import ch.dissem.bitmessage.entity.valueobject.Label
 | 
			
		||||
import ch.dissem.bitmessage.utils.ConversationServiceTest
 | 
			
		||||
import ch.dissem.bitmessage.utils.Singleton
 | 
			
		||||
import ch.dissem.bitmessage.utils.TestUtils
 | 
			
		||||
import org.hamcrest.CoreMatchers.`is`
 | 
			
		||||
import org.junit.Assert.assertThat
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
 | 
			
		||||
class MessageExportTest {
 | 
			
		||||
    val inbox = Label("Inbox", Label.Type.INBOX, 0x0000ff)
 | 
			
		||||
    val outbox = Label("Outbox", Label.Type.OUTBOX, 0x00ff00)
 | 
			
		||||
    val unread = Label("Unread", Label.Type.UNREAD, 0x000000)
 | 
			
		||||
    val trash = Label("Trash", Label.Type.TRASH, 0x555555)
 | 
			
		||||
 | 
			
		||||
    val labels = listOf(
 | 
			
		||||
        inbox,
 | 
			
		||||
        outbox,
 | 
			
		||||
        unread,
 | 
			
		||||
        trash
 | 
			
		||||
    )
 | 
			
		||||
    val labelMap = MessageExport.createLabelMap(labels)
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        TestUtils.mockedInternalContext(cryptography = BouncyCryptography())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `ensure labels are exported`() {
 | 
			
		||||
        val export = MessageExport.exportLabels(labels)
 | 
			
		||||
        print(export.toJsonString(true))
 | 
			
		||||
        assertThat(MessageExport.importLabels(export), `is`(labels))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `ensure messages are exported`() {
 | 
			
		||||
        val messages = listOf(
 | 
			
		||||
            Plaintext.Builder(Plaintext.Type.MSG)
 | 
			
		||||
                .ackData(Singleton.cryptography().randomBytes(32))
 | 
			
		||||
                .from(BitmessageAddress("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci"))
 | 
			
		||||
                .to(BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))
 | 
			
		||||
                .message("Subject", "Message")
 | 
			
		||||
                .status(Plaintext.Status.RECEIVED)
 | 
			
		||||
                .labels(listOf(inbox))
 | 
			
		||||
                .build(),
 | 
			
		||||
            Plaintext.Builder(Plaintext.Type.BROADCAST)
 | 
			
		||||
                .from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
 | 
			
		||||
                .message("Subject", "Message")
 | 
			
		||||
                .labels(listOf(inbox, unread))
 | 
			
		||||
                .status(Plaintext.Status.SENT)
 | 
			
		||||
                .build(),
 | 
			
		||||
            Plaintext.Builder(Plaintext.Type.MSG)
 | 
			
		||||
                .ttl(1)
 | 
			
		||||
                .message("subject", "message")
 | 
			
		||||
                .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
 | 
			
		||||
                .to(TestUtils.loadContact())
 | 
			
		||||
                .labels(listOf(trash))
 | 
			
		||||
                .status(Plaintext.Status.SENT_ACKNOWLEDGED)
 | 
			
		||||
                .build(),
 | 
			
		||||
            *ConversationServiceTest.conversation(
 | 
			
		||||
                TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"),
 | 
			
		||||
                TestUtils.loadContact()
 | 
			
		||||
            ).toTypedArray()
 | 
			
		||||
        )
 | 
			
		||||
        val export = MessageExport.exportMessages(messages)
 | 
			
		||||
        print(export.toJsonString(true))
 | 
			
		||||
        assertThat(MessageExport.importMessages(export, labelMap), `is`(messages))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
#Sun Jul 02 11:22:52 CEST 2017
 | 
			
		||||
#Mon Jul 17 06:32:41 CEST 2017
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
 | 
			
		||||
 
 | 
			
		||||
@@ -203,73 +203,77 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder {
 | 
			
		||||
                serverChannel.register(selector, OP_ACCEPT, null)
 | 
			
		||||
 | 
			
		||||
                while (selector.isOpen) {
 | 
			
		||||
                    selector.select(1000)
 | 
			
		||||
                    val keyIterator = selector.selectedKeys().iterator()
 | 
			
		||||
                    while (keyIterator.hasNext()) {
 | 
			
		||||
                        val key = keyIterator.next()
 | 
			
		||||
                        keyIterator.remove()
 | 
			
		||||
                        if (key.attachment() == null) {
 | 
			
		||||
                            try {
 | 
			
		||||
                                if (key.isAcceptable) {
 | 
			
		||||
                                    // handle accept
 | 
			
		||||
                                    try {
 | 
			
		||||
                                        val accepted = (key.channel() as ServerSocketChannel).accept()
 | 
			
		||||
                                        accepted.configureBlocking(false)
 | 
			
		||||
                                        val connection = Connection(ctx, SERVER,
 | 
			
		||||
                                            NetworkAddress(
 | 
			
		||||
                                                time = now,
 | 
			
		||||
                                                stream = 1L,
 | 
			
		||||
                                                socket = accepted.socket()!!
 | 
			
		||||
                                            ),
 | 
			
		||||
                                            requestedObjects, 0
 | 
			
		||||
                                        )
 | 
			
		||||
                                        connections.put(
 | 
			
		||||
                                            connection,
 | 
			
		||||
                                            accepted.register(selector, OP_READ or OP_WRITE, connection)
 | 
			
		||||
                                        )
 | 
			
		||||
                                    } catch (e: AsynchronousCloseException) {
 | 
			
		||||
                                        LOG.trace(e.message)
 | 
			
		||||
                                    } catch (e: IOException) {
 | 
			
		||||
                                        LOG.error(e.message, e)
 | 
			
		||||
                                    }
 | 
			
		||||
                    try {
 | 
			
		||||
                        selector.select(1000)
 | 
			
		||||
                        val keyIterator = selector.selectedKeys().iterator()
 | 
			
		||||
                        while (keyIterator.hasNext()) {
 | 
			
		||||
                            val key = keyIterator.next()
 | 
			
		||||
                            keyIterator.remove()
 | 
			
		||||
                            if (key.attachment() == null) {
 | 
			
		||||
                                try {
 | 
			
		||||
                                    if (key.isAcceptable) {
 | 
			
		||||
                                        // handle accept
 | 
			
		||||
                                        try {
 | 
			
		||||
                                            val accepted = (key.channel() as ServerSocketChannel).accept()
 | 
			
		||||
                                            accepted.configureBlocking(false)
 | 
			
		||||
                                            val connection = Connection(ctx, SERVER,
 | 
			
		||||
                                                NetworkAddress(
 | 
			
		||||
                                                    time = now,
 | 
			
		||||
                                                    stream = 1L,
 | 
			
		||||
                                                    socket = accepted.socket()!!
 | 
			
		||||
                                                ),
 | 
			
		||||
                                                requestedObjects, 0
 | 
			
		||||
                                            )
 | 
			
		||||
                                            connections.put(
 | 
			
		||||
                                                connection,
 | 
			
		||||
                                                accepted.register(selector, OP_READ or OP_WRITE, connection)
 | 
			
		||||
                                            )
 | 
			
		||||
                                        } catch (e: AsynchronousCloseException) {
 | 
			
		||||
                                            LOG.trace(e.message)
 | 
			
		||||
                                        } catch (e: IOException) {
 | 
			
		||||
                                            LOG.error(e.message, e)
 | 
			
		||||
                                        }
 | 
			
		||||
 | 
			
		||||
                                }
 | 
			
		||||
                            } catch (e: CancelledKeyException) {
 | 
			
		||||
                                LOG.debug(e.message, e)
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // handle read/write
 | 
			
		||||
                            val channel = key.channel() as SocketChannel
 | 
			
		||||
                            val connection = key.attachment() as Connection
 | 
			
		||||
                            try {
 | 
			
		||||
                                if (key.isConnectable) {
 | 
			
		||||
                                    if (!channel.finishConnect()) {
 | 
			
		||||
                                        continue
 | 
			
		||||
                                    }
 | 
			
		||||
                                } catch (e: CancelledKeyException) {
 | 
			
		||||
                                    LOG.debug(e.message, e)
 | 
			
		||||
                                }
 | 
			
		||||
                                if (key.isWritable) {
 | 
			
		||||
                                    write(channel, connection.io)
 | 
			
		||||
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // handle read/write
 | 
			
		||||
                                val channel = key.channel() as SocketChannel
 | 
			
		||||
                                val connection = key.attachment() as Connection
 | 
			
		||||
                                try {
 | 
			
		||||
                                    if (key.isConnectable) {
 | 
			
		||||
                                        if (!channel.finishConnect()) {
 | 
			
		||||
                                            continue
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (key.isWritable) {
 | 
			
		||||
                                        write(channel, connection.io)
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (key.isReadable) {
 | 
			
		||||
                                        read(channel, connection.io)
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (connection.state == Connection.State.DISCONNECTED) {
 | 
			
		||||
                                        key.interestOps(0)
 | 
			
		||||
                                        channel.close()
 | 
			
		||||
                                    } else if (connection.io.isWritePending) {
 | 
			
		||||
                                        key.interestOps(OP_READ or OP_WRITE)
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        key.interestOps(OP_READ)
 | 
			
		||||
                                    }
 | 
			
		||||
                                } catch (e: CancelledKeyException) {
 | 
			
		||||
                                    connection.disconnect()
 | 
			
		||||
                                } catch (e: NodeException) {
 | 
			
		||||
                                    connection.disconnect()
 | 
			
		||||
                                } catch (e: IOException) {
 | 
			
		||||
                                    connection.disconnect()
 | 
			
		||||
                                }
 | 
			
		||||
                                if (key.isReadable) {
 | 
			
		||||
                                    read(channel, connection.io)
 | 
			
		||||
                                }
 | 
			
		||||
                                if (connection.state == Connection.State.DISCONNECTED) {
 | 
			
		||||
                                    key.interestOps(0)
 | 
			
		||||
                                    channel.close()
 | 
			
		||||
                                } else if (connection.io.isWritePending) {
 | 
			
		||||
                                    key.interestOps(OP_READ or OP_WRITE)
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    key.interestOps(OP_READ)
 | 
			
		||||
                                }
 | 
			
		||||
                            } catch (e: CancelledKeyException) {
 | 
			
		||||
                                connection.disconnect()
 | 
			
		||||
                            } catch (e: NodeException) {
 | 
			
		||||
                                connection.disconnect()
 | 
			
		||||
                            } catch (e: IOException) {
 | 
			
		||||
                                connection.disconnect()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch (e: CancelledKeyException) {
 | 
			
		||||
                        LOG.debug(e.message, e)
 | 
			
		||||
                    }
 | 
			
		||||
                    // set interest ops
 | 
			
		||||
                    for ((connection, selectionKey) in connections) {
 | 
			
		||||
 
 | 
			
		||||
@@ -233,7 +233,7 @@ class NetworkHandlerTest {
 | 
			
		||||
        private val peerAddress = NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build()
 | 
			
		||||
 | 
			
		||||
        private fun shutdown(ctx: BitmessageContext) {
 | 
			
		||||
            if (!ctx.isRunning) return
 | 
			
		||||
            if (!ctx.isRunning()) return
 | 
			
		||||
 | 
			
		||||
            ctx.shutdown()
 | 
			
		||||
            do {
 | 
			
		||||
@@ -241,8 +241,7 @@ class NetworkHandlerTest {
 | 
			
		||||
                    Thread.sleep(100)
 | 
			
		||||
                } catch (ignore: InterruptedException) {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            } while (ctx.isRunning)
 | 
			
		||||
            } while (ctx.isRunning())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private fun shutdown(networkHandler: NetworkHandler) {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,22 +20,18 @@ import ch.dissem.bitmessage.entity.Plaintext
 | 
			
		||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
 | 
			
		||||
import ch.dissem.bitmessage.entity.valueobject.Label
 | 
			
		||||
import ch.dissem.bitmessage.ports.AbstractMessageRepository
 | 
			
		||||
import ch.dissem.bitmessage.ports.AlreadyStoredException
 | 
			
		||||
import ch.dissem.bitmessage.ports.MessageRepository
 | 
			
		||||
import ch.dissem.bitmessage.repository.JdbcHelper.Companion.writeBlob
 | 
			
		||||
import org.slf4j.LoggerFactory
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.sql.Connection
 | 
			
		||||
import java.sql.ResultSet
 | 
			
		||||
import java.sql.SQLException
 | 
			
		||||
import java.sql.Statement
 | 
			
		||||
import java.sql.*
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRepository(), MessageRepository {
 | 
			
		||||
 | 
			
		||||
    override fun findLabels(where: String): List<Label> {
 | 
			
		||||
        try {
 | 
			
		||||
            config.getConnection().use {
 | 
			
		||||
                connection ->
 | 
			
		||||
            config.getConnection().use { connection ->
 | 
			
		||||
                return findLabels(connection, where)
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: SQLException) {
 | 
			
		||||
@@ -51,12 +47,61 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
 | 
			
		||||
        } else {
 | 
			
		||||
            Label.Type.valueOf(typeName)
 | 
			
		||||
        }
 | 
			
		||||
        val label = Label(rs.getString("label"), type, rs.getInt("color"))
 | 
			
		||||
        val label = Label(rs.getString("label"), type, rs.getInt("color"), rs.getInt("ord"))
 | 
			
		||||
        label.id = rs.getLong("id")
 | 
			
		||||
 | 
			
		||||
        return label
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun save(label: Label) {
 | 
			
		||||
        config.getConnection().use { connection ->
 | 
			
		||||
            if (label.id != null) {
 | 
			
		||||
                connection.prepareStatement("UPDATE Label SET label=?, type=?, color=?, ord=? WHERE id=?").use { ps ->
 | 
			
		||||
                    ps.setString(1, label.toString())
 | 
			
		||||
                    ps.setString(2, label.type?.name)
 | 
			
		||||
                    ps.setInt(3, label.color)
 | 
			
		||||
                    ps.setInt(4, label.ord)
 | 
			
		||||
                    ps.setInt(5, label.id as Int)
 | 
			
		||||
                    ps.executeUpdate()
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                try {
 | 
			
		||||
                    connection.autoCommit = false
 | 
			
		||||
                    var exists = false
 | 
			
		||||
                    connection.prepareStatement("SELECT COUNT(1) FROM Label WHERE label=?").use { ps ->
 | 
			
		||||
                        ps.setString(1, label.toString())
 | 
			
		||||
                        val rs = ps.executeQuery()
 | 
			
		||||
                        if (rs.next()) {
 | 
			
		||||
                            exists = rs.getInt(1) > 0
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (exists) {
 | 
			
		||||
                        connection.prepareStatement("UPDATE Label SET type=?, color=?, ord=? WHERE label=?").use { ps ->
 | 
			
		||||
                            ps.setString(1, label.type?.name)
 | 
			
		||||
                            ps.setInt(2, label.color)
 | 
			
		||||
                            ps.setInt(3, label.ord)
 | 
			
		||||
                            ps.setString(4, label.toString())
 | 
			
		||||
                            ps.executeUpdate()
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        connection.prepareStatement("INSERT INTO Label (label, type, color, ord) VALUES (?, ?, ?, ?)").use { ps ->
 | 
			
		||||
                            ps.setString(1, label.toString())
 | 
			
		||||
                            ps.setString(2, label.type?.name)
 | 
			
		||||
                            ps.setInt(3, label.color)
 | 
			
		||||
                            ps.setInt(4, label.ord)
 | 
			
		||||
                            ps.executeUpdate()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    connection.commit()
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    connection.rollback()
 | 
			
		||||
                    throw e
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun countUnread(label: Label?): Int {
 | 
			
		||||
        val where = if (label == null) {
 | 
			
		||||
            ""
 | 
			
		||||
@@ -68,7 +113,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
 | 
			
		||||
        try {
 | 
			
		||||
            config.getConnection().use { connection ->
 | 
			
		||||
                connection.createStatement().use { stmt ->
 | 
			
		||||
                    stmt.executeQuery("SELECT count(*) FROM Message WHERE $where").use { rs ->
 | 
			
		||||
                    stmt.executeQuery("SELECT count(1) FROM Message WHERE $where").use { rs ->
 | 
			
		||||
                        if (rs.next()) {
 | 
			
		||||
                            return rs.getInt(1)
 | 
			
		||||
                        }
 | 
			
		||||
@@ -127,7 +172,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
 | 
			
		||||
        val result = ArrayList<Label>()
 | 
			
		||||
        try {
 | 
			
		||||
            connection.createStatement().use { stmt ->
 | 
			
		||||
                stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE $where").use { rs ->
 | 
			
		||||
                stmt.executeQuery("SELECT id, label, type, color, ord FROM Label WHERE $where").use { rs ->
 | 
			
		||||
                    while (rs.next()) {
 | 
			
		||||
                        result.add(getLabel(rs))
 | 
			
		||||
                    }
 | 
			
		||||
@@ -226,11 +271,15 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep
 | 
			
		||||
            ps.setObject(13, message.nextTry)
 | 
			
		||||
            ps.setObject(14, message.conversationId)
 | 
			
		||||
 | 
			
		||||
            ps.executeUpdate()
 | 
			
		||||
            // get generated id
 | 
			
		||||
            ps.generatedKeys.use { rs ->
 | 
			
		||||
                rs.next()
 | 
			
		||||
                message.id = rs.getLong(1)
 | 
			
		||||
            try {
 | 
			
		||||
                ps.executeUpdate()
 | 
			
		||||
                // get generated id
 | 
			
		||||
                ps.generatedKeys.use { rs ->
 | 
			
		||||
                    rs.next()
 | 
			
		||||
                    message.id = rs.getLong(1)
 | 
			
		||||
                }
 | 
			
		||||
            } catch (e: SQLIntegrityConstraintViolationException) {
 | 
			
		||||
                throw AlreadyStoredException(cause = e)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -14,4 +14,6 @@ include 'cryptography-sc'
 | 
			
		||||
 | 
			
		||||
include 'cryptography-bc'
 | 
			
		||||
 | 
			
		||||
include 'extensions'
 | 
			
		||||
include 'extensions'
 | 
			
		||||
 | 
			
		||||
include 'exports'
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user