diff --git a/library/base16/src/commonMain/kotlin/io/matthewnelson/encoding/base16/Base16.kt b/library/base16/src/commonMain/kotlin/io/matthewnelson/encoding/base16/Base16.kt index bcbf693..5f31d63 100644 --- a/library/base16/src/commonMain/kotlin/io/matthewnelson/encoding/base16/Base16.kt +++ b/library/base16/src/commonMain/kotlin/io/matthewnelson/encoding/base16/Base16.kt @@ -203,6 +203,7 @@ public class Base16: EncoderDecoder { lineBreakResetOnFlush, paddingChar = null, maxDecodeEmit = 1, + maxEncodeEmit = 2, backFillBuffers, ) { diff --git a/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/Base32.kt b/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/Base32.kt index c905a8a..0f40868 100644 --- a/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/Base32.kt +++ b/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/Base32.kt @@ -236,6 +236,7 @@ public sealed class Base32(config: C): EncoderDecoder< lineBreakResetOnFlush = false, paddingChar = null, maxDecodeEmit = 5, + maxEncodeEmit = calculateMaxEncodeEmit(emitSize = 8, insertionInterval = hyphenInterval.toInt()), backFillBuffers, ) { @@ -642,6 +643,7 @@ public sealed class Base32(config: C): EncoderDecoder< lineBreakResetOnFlush, paddingChar = '=', maxDecodeEmit = 5, + maxEncodeEmit = 8, backFillBuffers, ) { @@ -983,6 +985,7 @@ public sealed class Base32(config: C): EncoderDecoder< lineBreakResetOnFlush, paddingChar = '=', maxDecodeEmit = 5, + maxEncodeEmit = 8, backFillBuffers, ) { diff --git a/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/internal/-Config.kt b/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/internal/-Config.kt index 560a43c..b8fffbf 100644 --- a/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/internal/-Config.kt +++ b/library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/internal/-Config.kt @@ -25,10 +25,11 @@ internal inline fun ((Boolean, Boolean, Byte, Char?, Boolean, Boolean) -> Base32 b: Base32.Crockford.Builder, noinline crockford: (Base32.Crockford.Config, Any?) -> Base32.Crockford, ): Base32.Crockford { + val hyphenInterval = if (b._hyphenInterval <= 0) 0 else b._hyphenInterval if ( b._isLenient == Base32.Crockford.DELEGATE.config.isLenient && b._encodeLowercase == Base32.Crockford.DELEGATE.config.encodeLowercase - && b._hyphenInterval == Base32.Crockford.DELEGATE.config.hyphenInterval + && hyphenInterval == Base32.Crockford.DELEGATE.config.hyphenInterval && b._checkSymbol == Base32.Crockford.DELEGATE.config.checkSymbol && b._finalizeWhenFlushed == Base32.Crockford.DELEGATE.config.finalizeWhenFlushed && b._backFillBuffers == Base32.Crockford.DELEGATE.config.backFillBuffers @@ -38,7 +39,7 @@ internal inline fun ((Boolean, Boolean, Byte, Char?, Boolean, Boolean) -> Base32 val config = this( b._isLenient, b._encodeLowercase, - b._hyphenInterval, + hyphenInterval, b._checkSymbol, b._finalizeWhenFlushed, b._backFillBuffers, diff --git a/library/base64/src/commonMain/kotlin/io/matthewnelson/encoding/base64/Base64.kt b/library/base64/src/commonMain/kotlin/io/matthewnelson/encoding/base64/Base64.kt index 2367fa6..434ca04 100644 --- a/library/base64/src/commonMain/kotlin/io/matthewnelson/encoding/base64/Base64.kt +++ b/library/base64/src/commonMain/kotlin/io/matthewnelson/encoding/base64/Base64.kt @@ -254,6 +254,7 @@ public class Base64: EncoderDecoder { lineBreakResetOnFlush, paddingChar = '=', maxDecodeEmit = 3, + maxEncodeEmit = 4, backFillBuffers, ) { diff --git a/library/core/api/core.api b/library/core/api/core.api index 362494f..52a6929 100644 --- a/library/core/api/core.api +++ b/library/core/api/core.api @@ -124,9 +124,11 @@ public abstract class io/matthewnelson/encoding/core/EncoderDecoder$Config { public final field lineBreakInterval B public final field lineBreakResetOnFlush Z public final field maxDecodeEmit I + public final field maxEncodeEmit I public final field paddingChar Ljava/lang/Character; public fun (Ljava/lang/Boolean;BLjava/lang/Character;)V - protected fun (Ljava/lang/Boolean;BZLjava/lang/Character;IZ)V + protected fun (Ljava/lang/Boolean;BZLjava/lang/Character;IIZ)V + public static final fun calculateMaxEncodeEmit (II)I public final fun decodeOutMaxSize (J)J public final fun decodeOutMaxSizeOrFail (Lio/matthewnelson/encoding/core/util/DecoderInput;)I protected abstract fun decodeOutMaxSizeOrFailProtected (ILio/matthewnelson/encoding/core/util/DecoderInput;)I @@ -144,6 +146,7 @@ public abstract class io/matthewnelson/encoding/core/EncoderDecoder$Config { } public final class io/matthewnelson/encoding/core/EncoderDecoder$Config$Companion { + public final fun calculateMaxEncodeEmit (II)I public final fun outSizeExceedsMaxEncodingSizeException (Ljava/lang/Number;Ljava/lang/Number;)Lio/matthewnelson/encoding/core/EncodingSizeException; } diff --git a/library/core/api/core.klib.api b/library/core/api/core.klib.api index 3bfdf41..d4180d8 100644 --- a/library/core/api/core.klib.api +++ b/library/core/api/core.klib.api @@ -36,7 +36,7 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat final fun toString(): kotlin/String // io.matthewnelson.encoding.core/EncoderDecoder.toString|toString(){}[0] abstract class Config { // io.matthewnelson.encoding.core/EncoderDecoder.Config|null[0] - constructor (kotlin/Boolean?, kotlin/Byte, kotlin/Boolean, kotlin/Char?, kotlin/Int, kotlin/Boolean) // io.matthewnelson.encoding.core/EncoderDecoder.Config.|(kotlin.Boolean?;kotlin.Byte;kotlin.Boolean;kotlin.Char?;kotlin.Int;kotlin.Boolean){}[0] + constructor (kotlin/Boolean?, kotlin/Byte, kotlin/Boolean, kotlin/Char?, kotlin/Int, kotlin/Int, kotlin/Boolean) // io.matthewnelson.encoding.core/EncoderDecoder.Config.|(kotlin.Boolean?;kotlin.Byte;kotlin.Boolean;kotlin.Char?;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] constructor (kotlin/Boolean?, kotlin/Byte, kotlin/Char?) // io.matthewnelson.encoding.core/EncoderDecoder.Config.|(kotlin.Boolean?;kotlin.Byte;kotlin.Char?){}[0] final val backFillBuffers // io.matthewnelson.encoding.core/EncoderDecoder.Config.backFillBuffers|{}backFillBuffers[0] @@ -49,6 +49,8 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat final fun (): kotlin/Boolean // io.matthewnelson.encoding.core/EncoderDecoder.Config.lineBreakResetOnFlush.|(){}[0] final val maxDecodeEmit // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxDecodeEmit|{}maxDecodeEmit[0] final fun (): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxDecodeEmit.|(){}[0] + final val maxEncodeEmit // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmit|{}maxEncodeEmit[0] + final fun (): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmit.|(){}[0] final val paddingChar // io.matthewnelson.encoding.core/EncoderDecoder.Config.paddingChar|{}paddingChar[0] final fun (): kotlin/Char? // io.matthewnelson.encoding.core/EncoderDecoder.Config.paddingChar.|(){}[0] @@ -80,6 +82,7 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat } final object Companion { // io.matthewnelson.encoding.core/EncoderDecoder.Config.Companion|null[0] + final fun calculateMaxEncodeEmit(kotlin/Int, kotlin/Int): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.Companion.calculateMaxEncodeEmit|calculateMaxEncodeEmit(kotlin.Int;kotlin.Int){}[0] final fun outSizeExceedsMaxEncodingSizeException(kotlin/Number, kotlin/Number): io.matthewnelson.encoding.core/EncodingSizeException // io.matthewnelson.encoding.core/EncoderDecoder.Config.Companion.outSizeExceedsMaxEncodingSizeException|outSizeExceedsMaxEncodingSizeException(kotlin.Number;kotlin.Number){}[0] } } diff --git a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt index cf6c68f..e0c4245 100644 --- a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt +++ b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt @@ -22,6 +22,9 @@ import io.matthewnelson.encoding.core.internal.closedException import io.matthewnelson.encoding.core.internal.isSpaceOrNewLine import io.matthewnelson.encoding.core.util.DecoderInput import io.matthewnelson.encoding.core.util.LineBreakOutFeed +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.jvm.JvmField import kotlin.jvm.JvmStatic @@ -109,12 +112,39 @@ public abstract class EncoderDecoder(config: C): Encod * * Value will be between `1` and `255` (inclusive), or `-1` which indicates that the * [EncoderDecoder.Config] implementation has not updated to the new constructor introduced - * in version `2.6.0` and as such is unable to be used with `:core` module APIs dependent + * in version `2.6.0`, and as such is unable to be used with `:core` module APIs dependent * on this value (such as [Decoder.decodeBuffered] or [Decoder.decodeBufferedAsync]). * */ @JvmField public val maxDecodeEmit: Int, + /** + * The maximum number of characters that the implementation's [Encoder.Feed] can + * potentially emit on a single invocation of [Encoder.Feed.consume], [Encoder.Feed.flush], + * or [Encoder.Feed.doFinal]. + * + * For example, `Base16` encoding will emit `2` characters for every `1` byte of input, + * so its maximum emission is `2`. `Base32` encoding will emit `8` characters for every + * `5` bytes of input, so its maximum emission is `8`. `UTF8` "encoding" (i.e. UTF-8 byte + * to text transformations) can emit `4` characters for every `4` bytes of input (depending + * on the implementation), so its maximum emission would be `4`. + * + * **NOTE:** This value does **not** take into consideration the [lineBreakInterval] setting, + * or any other [LineBreakOutFeed]-like implementation that may inflate the maximum character + * emission size. Implementations must **only** consider their own [LineBreakOutFeed]-like + * implementation details (such as the hyphen interval for `Base32.Crockford`) when calculating + * their maximum character emission size. + * + * Value will be between `1` and `255` (inclusive), or `-1` which indicates that the + * [EncoderDecoder.Config] implementation has not updated to the new constructor introduced + * in version `2.6.0`, and as such is unable to be used with `:core` module APIs dependent + * on this value. + * + * @see [Companion.calculateMaxEncodeEmit] + * */ + @JvmField + public val maxEncodeEmit: Int, + /** * When the functions [Encoder.encodeToString], [Encoder.encodeToCharArray], * [Decoder.decodeToByteArray], [Decoder.decodeBuffered], and [Decoder.decodeBufferedAsync] @@ -139,7 +169,8 @@ public abstract class EncoderDecoder(config: C): Encod /** * Instantiates a new [Config] instance. * - * @throws [IllegalArgumentException] If [maxDecodeEmit] is less than `1` or greater than `255`. + * @throws [IllegalArgumentException] If [maxDecodeEmit] is less than `1` or greater than + * `255`. If [maxEncodeEmit] is less than `1` or greater than `255`. * */ protected constructor( isLenient: Boolean?, @@ -147,6 +178,7 @@ public abstract class EncoderDecoder(config: C): Encod lineBreakResetOnFlush: Boolean, paddingChar: Char?, maxDecodeEmit: Int, + maxEncodeEmit: Int, backFillBuffers: Boolean, ): this( isLenient = isLenient, @@ -154,11 +186,12 @@ public abstract class EncoderDecoder(config: C): Encod lineBreakResetOnFlush = lineBreakResetOnFlush, paddingChar = paddingChar, maxDecodeEmit = maxDecodeEmit, + maxEncodeEmit = maxEncodeEmit, backFillBuffers = backFillBuffers, unused = null, ) { - require(maxDecodeEmit > 0) { "maxDecodeEmit must be greater than 0" } - require(maxDecodeEmit < 256) { "maxDecodeEmit must be less than 256" } + checkMaxEmitSize(maxDecodeEmit) { "maxDecodeEmit" } + checkMaxEmitSize(maxEncodeEmit) { "maxEncodeEmit" } } /** @@ -339,6 +372,76 @@ public abstract class EncoderDecoder(config: C): Encod return outSize } + public companion object { + + /** + * Calculates and returns the maximum character emission size, given some sort of + * [emitSize] and desire to insert characters every [insertionInterval]. The way + * things are calculated are based on how [lineBreakInterval] operates, whereby + * if [insertionInterval] encoded characters have been output, the next encoded + * character output will be preceded with some arbitrary character (such as a + * hyphen for `Base32.Crockford`, which uses this function to calculate its final + * [maxEncodeEmit] value passed to the [Config] constructor). + * + * **NOTE:** Implementors of [Config] utilizing this to calculate their [maxEncodeEmit] + * values must consider that it may return a value greater than `255`, depending on the + * input arguments, resulting in an [IllegalArgumentException] when passed into the + * [Config] constructor. For example, an [insertionInterval] of `1` will inflate the + * provided [emitSize] by 2x. + * + * @param [emitSize] The number of characters that are expected to be emitted. + * @param [insertionInterval] The interval at which `1` character is to be inserted. + * + * @return The calculated emission size for a provided character [insertionInterval], or + * [emitSize] itself if [insertionInterval] is less than `1`. + * + * @see [lineBreakInterval] + * @see [maxEncodeEmit] + * + * @throws [IllegalArgumentException] If [emitSize] is less than `1` or greater than `255`. + * */ + @JvmStatic + public fun calculateMaxEncodeEmit(emitSize: Int, insertionInterval: Int): Int { + checkMaxEmitSize(emitSize) { "emitSize" } + if (insertionInterval <= 0) return emitSize + if (insertionInterval >= emitSize) return emitSize + 1 + + // Starting count at insertionInterval instead of 0 simulates the case of + // the very next output of emitSize encoded characters is to be preceded + // by the insertion character, such as a new line `\n`, whereby the max + // can be calculated. + // + // The limits for when this run an emitSize of 255 and an insertionInterval + // of emitSize - 1. Given how small actual emitSizes are expected to be, + // such as 8 for Base32, calculating things this way is OK with me... + var count = insertionInterval + var output = 0 + var i = 0 + while (i++ < emitSize) { + if (count == insertionInterval) { + output++ + count = 0 + } + output++ + count++ + } + return output + } + + /** + * Helper for generating an [EncodingSizeException] when the + * pre-calculated encoded/decoded output size exceeds the maximum for + * the given encoding/decoding specification. + * */ + @JvmStatic + public fun outSizeExceedsMaxEncodingSizeException( + inputSize: Number, + maxSize: Number, + ): EncodingSizeException = EncodingSizeException( + "Size[$inputSize] of input would exceed the maximum output Size[$maxSize] for this operation." + ) + } + /** * Calculate and return an exact (preferably), or maximum, size that an encoding would be * for the [unEncodedSize] data. @@ -430,6 +533,7 @@ public abstract class EncoderDecoder(config: C): Encod if (other.lineBreakResetOnFlush != this.lineBreakResetOnFlush) return false if (other.paddingChar != this.paddingChar) return false if (other.maxDecodeEmit != this.maxDecodeEmit) return false + if (other.maxEncodeEmit != this.maxEncodeEmit) return false if (other.backFillBuffers != this.backFillBuffers) return false if (other::class != this::class) return false return other._toStringAddSettings == this._toStringAddSettings @@ -443,6 +547,7 @@ public abstract class EncoderDecoder(config: C): Encod result = result * 31 + lineBreakResetOnFlush.hashCode() result = result * 31 + paddingChar.hashCode() result = result * 31 + maxDecodeEmit.hashCode() + result = result * 31 + maxEncodeEmit.hashCode() result = result * 31 + backFillBuffers.hashCode() result = result * 31 + this::class.hashCode() result = result * 31 + _toStringAddSettings.hashCode() @@ -462,6 +567,8 @@ public abstract class EncoderDecoder(config: C): Encod appendLine(paddingChar) append(" maxDecodeEmit: ") appendLine(maxDecodeEmit) + append(" maxEncodeEmit: ") + appendLine(maxEncodeEmit) append(" backFillBuffers: ") append(backFillBuffers) // last one uses append, not appendLine @@ -475,22 +582,6 @@ public abstract class EncoderDecoder(config: C): Encod append(']') }.toString() - public companion object { - - /** - * Helper for generating an [EncodingSizeException] when the - * pre-calculated encoded/decoded output size exceeds the maximum for - * the given encoding/decoding specification. - * */ - @JvmStatic - public fun outSizeExceedsMaxEncodingSizeException( - inputSize: Number, - maxSize: Number, - ): EncodingSizeException = EncodingSizeException( - "Size[$inputSize] of input would exceed the maximum output Size[$maxSize] for this operation." - ) - } - /** * DEPRECATED since `2.6.0` * @see [encodeOutMaxSize] @@ -520,9 +611,9 @@ public abstract class EncoderDecoder(config: C): Encod * @suppress * */ @Deprecated( - message = "Parameters, lineBreakResetOnFlush, maxDecodeEmit, and backFillBuffers were added. Use the new constructor.", + message = "Parameters, lineBreakResetOnFlush, maxDecodeEmit, maxEncodeEmit, and backFillBuffers were added. Use the new constructor.", replaceWith = ReplaceWith( - expression = "EncoderDecoder.Config(isLenient, lineBreakInterval, lineBreakResetOnFlush = false, paddingChar, maxDecodeEmit = 0 /* TODO */, backFillBuffers = true)"), + expression = "EncoderDecoder.Config(isLenient, lineBreakInterval, lineBreakResetOnFlush = false, paddingChar, maxDecodeEmit = 0 /* TODO */, maxEncodeEmit = 0 /* TODO */, backFillBuffers = true)"), level = DeprecationLevel.WARNING, ) public constructor( @@ -535,6 +626,7 @@ public abstract class EncoderDecoder(config: C): Encod lineBreakResetOnFlush = false, paddingChar = paddingChar, maxDecodeEmit = -1, // NOTE: NEVER change. + maxEncodeEmit = -1, // NOTE: NEVER change. backFillBuffers = true, unused = null, ) @@ -699,3 +791,17 @@ public abstract class EncoderDecoder(config: C): Encod private inline fun lineBreakIntervalOrZero(isLenient: Boolean?, interval: Byte): Byte { return if (isLenient != false && interval > 0) interval else 0 } + +@OptIn(ExperimentalContracts::class) +@Throws(IllegalArgumentException::class) +private inline fun checkMaxEmitSize(size: Int, parameterName: () -> String) { + contract { callsInPlace(parameterName, InvocationKind.AT_MOST_ONCE) } + if (size <= 0) { + val n = parameterName() + throw IllegalArgumentException("$n must be greater than 0") + } + if (size >= 256) { + val n = parameterName() + throw IllegalArgumentException("$n must be less than 256") + } +} diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt index 0cd604c..783a66b 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt @@ -60,6 +60,7 @@ class EncoderDecoderConfigUnitTest { decodeInputReturn = { -1 }, decodeReturn = { -1L }, maxDecodeEmit = 255, + maxEncodeEmit = 255, ) assertFailsWith { config.decodeOutMaxSize(5) } @@ -68,6 +69,9 @@ class EncoderDecoderConfigUnitTest { assertFailsWith { TestConfig(maxDecodeEmit = 0) } assertFailsWith { TestConfig(maxDecodeEmit = -1) } assertFailsWith { TestConfig(maxDecodeEmit = 256) } + assertFailsWith { TestConfig(maxEncodeEmit = 0) } + assertFailsWith { TestConfig(maxEncodeEmit = -1) } + assertFailsWith { TestConfig(maxEncodeEmit = 256) } } @Test @@ -181,4 +185,44 @@ class EncoderDecoderConfigUnitTest { } } + @Test + fun givenCalculateMaxEncodeEmit_whenInvalidEmitSize_thenThrowsException() { + assertFailsWith { EncoderDecoder.Config.calculateMaxEncodeEmit(0, 2) } + assertFailsWith { EncoderDecoder.Config.calculateMaxEncodeEmit(-1, 2) } + assertFailsWith { EncoderDecoder.Config.calculateMaxEncodeEmit(256, 2) } + } + + @Test + fun givenCalculateMaxEncodeEmit_whenIntervalLessThan1_thenReturnsEmitSize() { + val expected = 1 + val actual = EncoderDecoder.Config.calculateMaxEncodeEmit(expected, 0) + assertEquals(expected, actual) + } + + @Test + fun givenCalculateMaxEncodeEmit_whenIntervalGreaterOrEqualToThanEmitSize_thenReturnsExpected() { + val actual = EncoderDecoder.Config.calculateMaxEncodeEmit(1, 20) + assertEquals(1 + 1, actual) + } + + @Test + fun givenCalculateMaxEncodeEmit_whenIntervalIn1ToEmitSize_thenReturnsExpected() { + var i = 0 + arrayOf( + Triple(1, 1, 2), + Triple(2, 1, 4), + Triple(3, 2, 5), + Triple(10, 1, 20), + Triple(10, 2, 15), + Triple(10, 3, 14), + Triple(10, 4, 13), + Triple(10, 5, 12), + Triple(189, 5, 227), + Triple(189, 64, 192), + ).forEach { (size, interval, expected) -> + val actual = EncoderDecoder.Config.calculateMaxEncodeEmit(size, interval) + assertEquals(expected, actual, "arguments at index[$i]") + i++ + } + } } diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt index d6111c7..3081a79 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt @@ -26,6 +26,7 @@ class TestConfig public constructor( lineBreakResetOnFlush: Boolean = true, paddingChar: Char? = '=', maxDecodeEmit: Int = 1, + maxEncodeEmit: Int = 1, private val encodeReturn: (unEncodedSize: Long) -> Long = { -1L }, private val decodeInputReturn: (encodedSize: Int) -> Int = { -1 }, private val decodeReturn: (encodedSize: Long) -> Long = { -1L }, @@ -35,6 +36,7 @@ class TestConfig public constructor( lineBreakResetOnFlush, paddingChar, maxDecodeEmit, + maxEncodeEmit, backFillBuffers = true, ) { override fun decodeOutMaxSizeProtected(encodedSize: Long): Long { diff --git a/library/utf8/src/commonMain/kotlin/io/matthewnelson/encoding/utf8/UTF8.kt b/library/utf8/src/commonMain/kotlin/io/matthewnelson/encoding/utf8/UTF8.kt index 68be17a..3044484 100644 --- a/library/utf8/src/commonMain/kotlin/io/matthewnelson/encoding/utf8/UTF8.kt +++ b/library/utf8/src/commonMain/kotlin/io/matthewnelson/encoding/utf8/UTF8.kt @@ -240,6 +240,7 @@ public open class UTF8: EncoderDecoder { lineBreakResetOnFlush = false, paddingChar = null, maxDecodeEmit = (replacementStrategy.size * 2).coerceAtLeast(4), + maxEncodeEmit = if (replacementStrategy.size == ReplacementStrategy.THROW.size) 2 else 4, backFillBuffers, ) {