diff --git a/build.gradle b/build.gradle index 41f5805..669ad8e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.3' + ext.kotlin_version = '1.1.3-2' repositories { mavenCentral() } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt index b6d2108..d426dac 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt @@ -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") diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt index 51e0d02..9279dc0 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt @@ -30,6 +30,8 @@ interface MessageRepository { fun countUnread(label: Label?): Int + fun getAllMessages(): List + fun getMessage(id: Any): Plaintext fun getMessage(iv: InventoryVector): Plaintext? diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base58.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base58.kt index 7f98f93..216e07d 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base58.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base58.kt @@ -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 diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base64.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base64.kt new file mode 100644 index 0000000..f37415e --- /dev/null +++ b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base64.kt @@ -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() + } +} diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/utils/UnixTime.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/UnixTime.kt index d21bc2e..b542bd9 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/utils/UnixTime.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/utils/UnixTime.kt @@ -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) diff --git a/core/src/main/resources/nodes.txt b/core/src/main/resources/nodes.txt index bce982e..23e85d2 100644 --- a/core/src/main/resources/nodes.txt +++ b/core/src/main/resources/nodes.txt @@ -1,6 +1,6 @@ [stream 1] -dissem.ch:8444 +bitmessage.dissem.ch:8444 bootstrap8080.bitmessage.org:8080 bootstrap8444.bitmessage.org:8444 diff --git a/core/src/test/kotlin/ch/dissem/bitmessage/utils/Base64Test.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/Base64Test.kt new file mode 100644 index 0000000..35f9e3a --- /dev/null +++ b/core/src/test/kotlin/ch/dissem/bitmessage/utils/Base64Test.kt @@ -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)) + } + } +} diff --git a/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt b/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt index 5a5d6bc..e429881 100644 --- a/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt +++ b/exports/src/main/kotlin/ch/dissem/bitmessage/exports/ContactExport.kt @@ -19,18 +19,17 @@ 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 -import java.util.* /** * Exports and imports contacts and identities */ object ContactExport { fun exportContacts(contacts: List<BitmessageAddress>, includePrivateKey: Boolean = false) = json { - val base64 = Base64.getEncoder() array( contacts.map { obj( @@ -41,10 +40,10 @@ object ContactExport { "pubkey" to it.pubkey?.let { val out = ByteArrayOutputStream() it.writeUnencrypted(out) - base64.encodeToString(out.toByteArray()) + Base64.encodeToString(out.toByteArray()) }, "privateKey" to if (includePrivateKey) { - it.privateKey?.let { base64.encodeToString(Encode.bytes(it)) } + it.privateKey?.let { Base64.encodeToString(Encode.bytes(it)) } } else { null } @@ -55,8 +54,7 @@ object ContactExport { fun importContacts(input: JsonArray<*>): List<BitmessageAddress> { return input.filterIsInstance(JsonObject::class.java).map { json -> - val base64 = Base64.getDecoder() - fun JsonObject.bytes(fieldName: String) = string(fieldName)?.let { base64.decode(it) } + 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) diff --git a/exports/src/main/kotlin/ch/dissem/bitmessage/exports/MessageExport.kt b/exports/src/main/kotlin/ch/dissem/bitmessage/exports/MessageExport.kt index 8b4bd4e..2b195cd 100644 --- a/exports/src/main/kotlin/ch/dissem/bitmessage/exports/MessageExport.kt +++ b/exports/src/main/kotlin/ch/dissem/bitmessage/exports/MessageExport.kt @@ -20,6 +20,7 @@ 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.* @@ -42,7 +43,6 @@ object MessageExport { } fun exportMessages(messages: List<Plaintext>) = json { - val base64 = Base64.getEncoder() array(messages.map { obj( "type" to it.type.name, @@ -52,13 +52,13 @@ object MessageExport { "body" to it.text, "conversationId" to it.conversationId.toString(), - "msgId" to it.inventoryVector?.hash?.let { base64.encodeToString(it) }, + "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) }, + "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, @@ -69,8 +69,7 @@ object MessageExport { fun importMessages(input: JsonArray<*>, labels: Map<String, Label>): List<Plaintext> { return input.filterIsInstance(JsonObject::class.java).map { json -> - val base64 = Base64.getDecoder() - fun JsonObject.bytes(fieldName: String) = string(fieldName)?.let { base64.decode(it) } + 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) })