org.kin.stellarfork.codec.BaseNCodec.kt Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.kin.stellarfork.codec
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/ /**
* Abstract superclass for Base-N encoders and decoders.
*
*
*
* This class is not thread-safe.
* Each thread should use its own instance.
*
*/
abstract class BaseNCodec protected constructor(
/**
* Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32
*/
private val unencodedBlockSize: Int,
/**
* Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32
*/
private val encodedBlockSize: Int,
lineLength: Int,
/**
* Size of chunk separator. Not used unless [.lineLength] > 0.
*/
private val chunkSeparatorLength: Int
) : BinaryEncoder, BinaryDecoder {
protected val PAD = PAD_DEFAULT // instance variable just in case it needs to vary later
/**
* Chunksize for encoding. Not used when decoding.
* A value of zero or less implies no chunking of the encoded data.
* Rounded down to nearest multiple of encodedBlockSize.
*/
@JvmField
val lineLength: Int =
if (lineLength > 0 && chunkSeparatorLength > 0) lineLength / encodedBlockSize * encodedBlockSize else 0
/**
* Buffer for streaming.
*/
protected var buffer: ByteArray? = null
/**
* Position where next character should be written in the buffer.
*/
protected var pos = 0
/**
* Position where next character should be read from the buffer.
*/
private var readPos = 0
/**
* Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
* and must be thrown away.
*/
protected var eof = false
/**
* Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to
* make sure each encoded line never goes beyond lineLength (if lineLength > 0).
*/
protected var currentLinePos = 0
/**
* Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding.
* This variable helps track that.
*/
protected var modulus = 0
/**
* Returns true if this object has buffered data for reading.
*
* @return true if there is data still available for reading.
*/
fun hasData(): Boolean { // package protected for access from I/O streams
return buffer != null
}
/**
* Returns the amount of buffered data available for reading.
*
* @return The amount of buffered data available for reading.
*/
fun available(): Int { // package protected for access from I/O streams
return if (buffer != null) pos - readPos else 0
}
/**
* Increases our buffer by the [.DEFAULT_BUFFER_RESIZE_FACTOR].
*/
private fun resizeBuffer() {
if (buffer == null) {
buffer = ByteArray(defaultBufferSize)
pos = 0
readPos = 0
} else {
buffer?.let { buffer ->
val b = ByteArray(buffer.size * DEFAULT_BUFFER_RESIZE_FACTOR)
System.arraycopy(buffer, 0, b, 0, buffer.size)
this.buffer = b
}
}
}
/**
* Ensure that the buffer has room for `size` bytes
*
* @param size minimum spare space required
*/
protected fun ensureBufferSize(size: Int) {
if (buffer == null || buffer!!.size < pos + size) {
resizeBuffer()
}
}
/**
* Extracts buffered data into the provided byte[] array, starting at position bPos,
* up to a maximum of bAvail bytes. Returns how many bytes were actually extracted.
*
* @param b byte[] array to extract the buffered data into.
* @param bPos position in byte[] array to start extraction at.
* @param bAvail amount of bytes we're allowed to extract. We may extract fewer (if fewer are available).
* @return The number of bytes successfully extracted into the provided byte[] array.
*/
fun readResults(
b: ByteArray?,
bPos: Int,
bAvail: Int
): Int { // package protected for access from I/O streams
buffer?.let { buffer ->
val len = Math.min(available(), bAvail)
System.arraycopy(buffer, readPos, b, bPos, len)
readPos += len
if (readPos >= pos) {
this.buffer = null // so hasData() will return false, and this method can return -1
}
return len
}
return if (eof) -1 else 0
}
/**
* Resets this object to its initial newly constructed state.
*/
private fun reset() {
buffer = null
pos = 0
readPos = 0
currentLinePos = 0
modulus = 0
eof = false
}
/**
* Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of the
* Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
*
* @param source Object to encode
* @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied.
* @throws EncoderException if the parameter supplied is not of type byte[]
*/
@Throws(EncoderException::class)
override fun encode(source: Any?): Any? {
if (source !is ByteArray) {
throw EncoderException("Parameter supplied to Base-N encode is not a byte[]")
}
return encode(source)
}
/**
* Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
*
* @param pArray a byte array containing binary data
* @return A String containing only Base-N character data
*/
fun encodeToString(pArray: ByteArray?): String? {
return StringUtils.newStringUtf8(encode(pArray))
}
/**
* Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of the
* Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String.
*
* @param source Object to decode
* @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String supplied.
* @throws DecoderException if the parameter supplied is not of type byte[]
*/
@Throws(DecoderException::class)
override fun decode(source: Any?): Any? {
return if (source is ByteArray) {
decode(source)
} else if (source is String) {
decode(source)
} else {
throw DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String")
}
}
/**
* Decodes a String containing characters in the Base-N alphabet.
*
* @param pArray A String containing Base-N character data
* @return a byte array containing binary data
*/
fun decode(pArray: String?): ByteArray? {
return decode(StringUtils.getBytesUtf8(pArray))
}
/**
* Decodes a byte[] containing characters in the Base-N alphabet.
*
* @param source A byte array containing Base-N character data
* @return a byte array containing binary data
*/
override fun decode(source: ByteArray?): ByteArray? {
reset()
if (source == null || source.isEmpty()) {
return source
}
decode(source, 0, source.size)
decode(source, 0, -1) // Notify decoder of EOF.
val result = ByteArray(pos)
readResults(result, 0, result.size)
return result
}
/**
* Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
*
* @param source a byte array containing binary data
* @return A byte array containing only the basen alphabetic character data
*/
override fun encode(source: ByteArray?): ByteArray? {
reset()
if (source == null || source.isEmpty()) {
return source
}
encode(source, 0, source.size)
encode(source, 0, -1) // Notify encoder of EOF.
val buf = ByteArray(pos - readPos)
readResults(buf, 0, buf.size)
return buf
}
/**
* Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet.
* Uses UTF8 encoding.
*
* @param pArray a byte array containing binary data
* @return String containing only character data in the appropriate alphabet.
*/
fun encodeAsString(pArray: ByteArray): String {
return StringUtils.newStringUtf8(encode(pArray))!!
}
abstract fun encode(
pArray: ByteArray,
i: Int,
length: Int
) // package protected for access from I/O streams
abstract fun decode(
pArray: ByteArray,
i: Int,
length: Int
) // package protected for access from I/O streams
/**
* Returns whether or not the `octet` is in the current alphabet.
* Does not allow whitespace or pad.
*
* @param value The value to test
* @return `true` if the value is defined in the current alphabet, `false` otherwise.
*/
abstract fun isInAlphabet(value: Byte): Boolean
/**
* Tests a given byte array to see if it contains only valid characters within the alphabet.
* The method optionally treats whitespace and pad as valid.
*
* @param arrayOctet byte array to test
* @param allowWSPad if `true`, then whitespace and PAD are also allowed
* @return `true` if all bytes are valid characters in the alphabet or if the byte array is empty;
* `false`, otherwise
*/
fun isInAlphabet(arrayOctet: ByteArray?, allowWSPad: Boolean): Boolean {
arrayOctet?.let { theArrayOctet->
for (i in theArrayOctet.indices) {
if (!isInAlphabet(theArrayOctet[i]) &&
(!allowWSPad || theArrayOctet[i] != PAD && !isWhiteSpace(
theArrayOctet[i]
))
) {
return false
}
}
}
return true
}
/**
* Tests a given String to see if it contains only valid characters within the alphabet.
* The method treats whitespace and PAD as valid.
*
* @param basen String to test
* @return `true` if all characters in the String are valid characters in the alphabet or if
* the String is empty; `false`, otherwise
* @see .isInAlphabet
*/
fun isInAlphabet(basen: String): Boolean {
return isInAlphabet(StringUtils.getBytesUtf8(basen), true)
}
/**
* Tests a given byte array to see if it contains any characters within the alphabet or PAD.
*
*
* Intended for use in checking line-ending arrays
*
* @param arrayOctet byte array to test
* @return `true` if any byte is a valid character in the alphabet or PAD; `false` otherwise
*/
fun containsAlphabetOrPad(arrayOctet: ByteArray?): Boolean {
if (arrayOctet == null) {
return false
}
for (i in arrayOctet.indices) {
if (PAD == arrayOctet[i] || isInAlphabet(arrayOctet[i])) {
return true
}
}
return false
}
/**
* Calculates the amount of space needed to encode the supplied array.
*
* @param pArray byte[] array which will later be encoded
* @return amount of space needed to encoded the supplied array.
* Returns a long since a max-len array will require > Integer.MAX_VALUE
*/
fun getEncodedLength(pArray: ByteArray): Long { // Calculate non-chunked size - rounded up to allow for padding
// cast to long is needed to avoid possibility of overflow
var len =
(pArray.size + unencodedBlockSize - 1) / unencodedBlockSize * encodedBlockSize.toLong()
if (lineLength > 0) { // We're using chunking
// Round up to nearest multiple
len += (len + lineLength - 1) / lineLength * chunkSeparatorLength
}
return len
}
companion object {
/**
* MIME chunk size per RFC 2045 section 6.8.
*
*
*
* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
* equal signs.
*
*
* @see [RFC 2045 section 6.8](http://www.ietf.org/rfc/rfc2045.txt)
*/
const val MIME_CHUNK_SIZE = 76
/**
* PEM chunk size per RFC 1421 section 4.3.2.4.
*
*
*
* The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
* equal signs.
*
*
* @see [RFC 1421 section 4.3.2.4](http://tools.ietf.org/html/rfc1421)
*/
const val PEM_CHUNK_SIZE = 64
private const val DEFAULT_BUFFER_RESIZE_FACTOR = 2
/**
* Get the default buffer size. Can be overridden.
*
* @return [.DEFAULT_BUFFER_SIZE]
*/
/**
* Defines the default buffer size - currently {@value}
* - must be large enough for at least one encoded block+separator
*/
protected const val defaultBufferSize = 8192
/**
* Mask used to extract 8 bits, used in decoding bytes
*/
@JvmStatic
protected val MASK_8BITS = 0xff
/**
* Byte used to pad output.
*/
@JvmStatic
protected val PAD_DEFAULT = '=' // Allow static access to default
.toByte()
/**
* Checks if a byte value is whitespace or not.
* Whitespace is taken to mean: space, tab, CR, LF
*
* @param byteToCheck the byte to check
* @return true if byte is whitespace, false otherwise
*/
@JvmStatic
fun isWhiteSpace(byteToCheck: Byte): Boolean {
return when (byteToCheck.toChar()) {
' ', '\n', '\r', '\t' -> true
else -> false
}
}
@JvmStatic
fun byteArrayFromChars(vararg chars: Char) = chars.map { it.toByte() }.toByteArray()
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy