Skip to content

Commit c4f05f5

Browse files
authored
Use Int and Long, instead of arrays, when buffering input (#236)
1 parent 42d8495 commit c4f05f5

File tree

2 files changed

+65
-120
lines changed
  • library
    • base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32
    • base64/src/commonMain/kotlin/io/matthewnelson/encoding/base64

2 files changed

+65
-120
lines changed

library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/Base32.kt

Lines changed: 47 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,8 +1140,8 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
11401140

11411141
protected abstract fun Int.decodeDiff(): Int
11421142

1143-
private val buf = IntArray(7)
1144-
private var iBuf = 0
1143+
private var _count = 0
1144+
private var _word = 0L
11451145

11461146
override fun consumeProtected(input: Char) {
11471147
val code = input.code
@@ -1150,21 +1150,15 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
11501150
throw MalformedEncodingException("Char[$input] is not a valid ${name()} character")
11511151
}
11521152

1153-
if (iBuf < 7) {
1154-
buf[iBuf++] = code + diff
1153+
// Append each character's 5 bits to the word
1154+
val word = _word shl 5 or (code + diff).toLong()
1155+
1156+
if (_count++ < 7) {
1157+
_word = word
11551158
return // Await more input
11561159
}
1157-
1158-
// Append each character's 5 bits to the word
1159-
var word: Long = buf[0].toLong()
1160-
word = word shl 5 or buf[1].toLong()
1161-
word = word shl 5 or buf[2].toLong()
1162-
word = word shl 5 or buf[3].toLong()
1163-
word = word shl 5 or buf[4].toLong()
1164-
word = word shl 5 or buf[5].toLong()
1165-
word = word shl 5 or buf[6].toLong()
1166-
word = word shl 5 or (code + diff).toLong()
1167-
iBuf = 0
1160+
_count = 0
1161+
_word = 0L
11681162

11691163
// For every 8 characters of input, 40 bits of output are accumulated. Emit 5 bytes.
11701164
_out.output((word shr 32).toByte())
@@ -1175,47 +1169,29 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
11751169
}
11761170

11771171
override fun doFinalProtected() {
1178-
if (iBuf == 0) return buf.fill(0)
1172+
val count = _count
1173+
if (count == 0) return
1174+
var word = _word
1175+
_count = 0
1176+
_word = 0L
11791177

1180-
if (iBuf == 1) {
1178+
if (count == 1) {
11811179
// 5*1 = 5 bits. Truncated, fail.
1182-
iBuf = 0
1183-
buf.fill(0)
11841180
throw FeedBuffer.truncatedInputEncodingException(1)
11851181
}
1186-
if (iBuf == 3) {
1187-
// 5*3 = 15 bits. Truncated, fail.
1188-
iBuf = 0
1189-
buf.fill(0)
1190-
throw FeedBuffer.truncatedInputEncodingException(3)
1191-
}
1192-
if (iBuf == 6) {
1193-
// 5*6 = 30 bits. Truncated, fail.
1194-
iBuf = 0
1195-
buf.fill(0)
1196-
throw FeedBuffer.truncatedInputEncodingException(6)
1197-
}
1198-
1199-
// iBuf == 2, 4, 5 or 7
1200-
// Append each character's 5 bits to the word
1201-
var word: Long = buf[0].toLong()
1202-
word = word shl 5 or buf[1].toLong()
1203-
if (iBuf == 2) {
1204-
iBuf = 0
1205-
buf.fill(0)
1182+
if (count == 2) {
12061183
// 5*2 = 10 bits. Drop 2
12071184
word = word shr 2
12081185

12091186
// 8/8 = 1 byte
12101187
_out.output((word ).toByte())
12111188
return
12121189
}
1213-
1214-
word = word shl 5 or buf[2].toLong()
1215-
word = word shl 5 or buf[3].toLong()
1216-
if (iBuf == 4) {
1217-
iBuf = 0
1218-
buf.fill(0)
1190+
if (count == 3) {
1191+
// 5*3 = 15 bits. Truncated, fail.
1192+
throw FeedBuffer.truncatedInputEncodingException(3)
1193+
}
1194+
if (count == 4) {
12191195
// 5*4 = 20 bits. Drop 4
12201196
word = word shr 4
12211197

@@ -1224,11 +1200,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
12241200
_out.output((word ).toByte())
12251201
return
12261202
}
1227-
1228-
word = word shl 5 or buf[4].toLong()
1229-
if (iBuf == 5) {
1230-
iBuf = 0
1231-
buf.fill(0)
1203+
if (count == 5) {
12321204
// 5*5 = 25 bits. Drop 1
12331205
word = word shr 1
12341206

@@ -1238,12 +1210,11 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
12381210
_out.output((word ).toByte())
12391211
return
12401212
}
1241-
1242-
word = word shl 5 or buf[5].toLong()
1243-
word = word shl 5 or buf[6].toLong()
1244-
if (iBuf == 7) {
1245-
iBuf = 0
1246-
buf.fill(0)
1213+
if (count == 6) {
1214+
// 5*6 = 30 bits. Truncated, fail.
1215+
throw FeedBuffer.truncatedInputEncodingException(6)
1216+
}
1217+
if (count == 7) {
12471218
// 5*7 = 35 bits. Drop 3
12481219
word = word shr 3
12491220

@@ -1256,7 +1227,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
12561227
}
12571228

12581229
// "Should" never make it here
1259-
error("Illegal configuration >> iBuf[$iBuf] - buf[${buf[0]}, ${buf[1]}, ${buf[2]}, ${buf[3]}, ${buf[4]}, ${buf[5]}, ${buf[6]}]")
1230+
error("Illegal configuration >> count[$count]")
12601231
}
12611232
}
12621233

@@ -1265,22 +1236,19 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
12651236
protected abstract fun Encoder.OutFeed.output1(i: Int)
12661237
protected abstract fun Encoder.OutFeed.outputPadding(n: Int)
12671238

1268-
private val buf = ByteArray(4)
1269-
private var iBuf = 0
1239+
private var _count = 0
1240+
private var _word = 0L
12701241

12711242
final override fun consumeProtected(input: Byte) {
1272-
if (iBuf < 4) {
1273-
buf[iBuf++] = input
1243+
// Append each character's 8 bits to the word
1244+
val word = (_word shl 8) + input.toBits()
1245+
1246+
if (_count++ < 4) {
1247+
_word = word
12741248
return // Await more input
12751249
}
1276-
1277-
// Append each character's 8 bits to the word
1278-
var word: Long = buf[0].toBits()
1279-
word = (word shl 8) + buf[1].toBits()
1280-
word = (word shl 8) + buf[2].toBits()
1281-
word = (word shl 8) + buf[3].toBits()
1282-
word = (word shl 8) + input.toBits()
1283-
iBuf = 0
1250+
_count = 0
1251+
_word = 0L
12841252

12851253
// For every 5 bytes of input, 40 bits of output are accumulated. Emit 8 characters.
12861254
_out.output1(i = (word shr 35 and 0x1fL).toInt()) // 40-1*5 = 35
@@ -1294,38 +1262,30 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
12941262
}
12951263

12961264
final override fun doFinalProtected() {
1297-
if (iBuf == 0) {
1298-
buf.fill(0)
1265+
val count = _count
1266+
if (count == 0) {
12991267
// Still call with 0 b/c Crockford uses to append its check symbol
13001268
return _out.outputPadding(n = 0)
13011269
}
1270+
val word = _word
1271+
_count = 0
1272+
_word = 0L
13021273

1303-
var word: Long = buf[0].toBits()
1304-
if (iBuf == 1) {
1305-
iBuf = 0
1306-
buf.fill(0)
1274+
if (count == 1) {
13071275
// 8*1 = 8 bits
13081276
_out.output1(i = (word shr 3 and 0x1fL).toInt()) // 8-1*5 = 3
13091277
_out.output1(i = (word shl 2 and 0x1fL).toInt()) // 5-3 = 2
13101278
return _out.outputPadding(n = 6)
13111279
}
1312-
1313-
word = (word shl 8) + buf[1].toBits()
1314-
if (iBuf == 2) {
1315-
iBuf = 0
1316-
buf.fill(0)
1280+
if (count == 2) {
13171281
// 8*2 = 16 bits
13181282
_out.output1(i = (word shr 11 and 0x1fL).toInt()) // 16-1*5 = 11
13191283
_out.output1(i = (word shr 6 and 0x1fL).toInt()) // 16-2*5 = 6
13201284
_out.output1(i = (word shr 1 and 0x1fL).toInt()) // 16-3*5 = 1
13211285
_out.output1(i = (word shl 4 and 0x1fL).toInt()) // 5-1 = 4
13221286
return _out.outputPadding(n = 4)
13231287
}
1324-
1325-
word = (word shl 8) + buf[2].toBits()
1326-
if (iBuf == 3) {
1327-
iBuf = 0
1328-
buf.fill(0)
1288+
if (count == 3) {
13291289
// 8*3 = 24 bits
13301290
_out.output1(i = (word shr 19 and 0x1fL).toInt()) // 24-1*5 = 19
13311291
_out.output1(i = (word shr 14 and 0x1fL).toInt()) // 24-2*5 = 14
@@ -1334,11 +1294,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
13341294
_out.output1(i = (word shl 1 and 0x1fL).toInt()) // 5-4 = 1
13351295
return _out.outputPadding(n = 3)
13361296
}
1337-
1338-
word = (word shl 8) + buf[3].toBits()
1339-
if (iBuf == 4) {
1340-
iBuf = 0
1341-
buf.fill(0)
1297+
if (count == 4) {
13421298
// 8*4 = 32 bits
13431299
_out.output1(i = (word shr 27 and 0x1fL).toInt()) // 32-1*5 = 27
13441300
_out.output1(i = (word shr 22 and 0x1fL).toInt()) // 32-2*5 = 22
@@ -1351,7 +1307,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
13511307
}
13521308

13531309
// "Should" never make it here
1354-
error("Illegal configuration >> iBuf[$iBuf] - buf[${buf[0]}, ${buf[1]}, ${buf[2]}, ${buf[3]}]")
1310+
error("Illegal configuration >> count[$count]")
13551311
}
13561312

13571313
private inline fun Byte.toBits(): Long = if (this < 0) this + 256L else toLong()

library/base64/src/commonMain/kotlin/io/matthewnelson/encoding/base64/Base64.kt

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,8 @@ public class Base64: EncoderDecoder<Base64.Config> {
417417

418418
private inner class DecoderFeed(out: Decoder.OutFeed): Decoder<Config>.Feed(_out = out) {
419419

420-
private val buf = IntArray(3)
421-
private var iBuf = 0
420+
private var _count = 0
421+
private var _word = 0
422422

423423
override fun consumeProtected(input: Char) {
424424
val code = input.code
@@ -460,17 +460,15 @@ public class Base64: EncoderDecoder<Base64.Config> {
460460
throw MalformedEncodingException("Char[$input] is not a valid $NAME character")
461461
}
462462

463-
if (iBuf < 3) {
464-
buf[iBuf++] = code + diff
463+
// Append each character's 6 bits to the word
464+
val word = _word shl 6 or (code + diff)
465+
466+
if (_count++ < 3) {
467+
_word = word
465468
return // Await more input
466469
}
467-
468-
// Append each character's 6 bits to the word
469-
var word = buf[0]
470-
word = word shl 6 or buf[1]
471-
word = word shl 6 or buf[2]
472-
word = word shl 6 or (code + diff)
473-
iBuf = 0
470+
_count = 0
471+
_word = 0
474472

475473
// For every 4 characters of input, 24 bits of output are accumulated. Emit 3 bytes.
476474
_out.output((word shr 16).toByte())
@@ -479,32 +477,23 @@ public class Base64: EncoderDecoder<Base64.Config> {
479477
}
480478

481479
override fun doFinalProtected() {
482-
if (iBuf == 0) return buf.fill(0)
480+
val count = _count
481+
if (count == 0) return
482+
var word = _word
483+
_count = 0
484+
_word = 0
483485

484-
if (iBuf == 1) {
485-
iBuf = 0
486-
buf.fill(0)
486+
if (count == 1) {
487487
// 1 character followed by "===". But 6 bits is a truncated byte, fail.
488488
throw FeedBuffer.truncatedInputEncodingException(1)
489489
}
490-
491-
// Append each character's 6 bits to the word
492-
var word = buf[0]
493-
word = word shl 6 or buf[1]
494-
495-
if (iBuf == 2) {
496-
iBuf = 0
497-
buf.fill(0)
490+
if (count == 2) {
498491
// 2 characters followed by "==". Emit 1 byte for 8 of those 12 bits.
499492
word = word shl 12
500493
_out.output((word shr 16).toByte())
501494
return
502495
}
503-
504-
word = word shl 6 or buf[2]
505-
if (iBuf == 3) {
506-
iBuf = 0
507-
buf.fill(0)
496+
if (count == 3) {
508497
// 3 characters followed by "=". Emit 2 byte for 16 of those 18 bits.
509498
word = word shl 6
510499
_out.output((word shr 16).toByte())
@@ -513,7 +502,7 @@ public class Base64: EncoderDecoder<Base64.Config> {
513502
}
514503

515504
// "Should" never make it here
516-
error("Illegal configuration >> iBuf[$iBuf] - buf[${buf[0]}, ${buf[1]}, ${buf[2]}]")
505+
error("Illegal configuration >> count[$count]")
517506
}
518507
}
519508

0 commit comments

Comments
 (0)