Skip to content

Commit

Permalink
Allow padding in base 32,36 and add base 16,64 encode functions (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
oharaandrew314 authored Mar 25, 2024
1 parent e633c92 commit c4d1c71
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 5 deletions.
27 changes: 22 additions & 5 deletions values4k/src/main/kotlin/dev/forkhandles/values/factories.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import java.time.format.DateTimeFormatter.ISO_OFFSET_TIME
import java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME
import java.time.format.DateTimeFormatter.ofPattern
import java.util.UUID
import java.util.Base64

private val rfcBase64Alphabet get() = "^[0-9A-Za-z+/]+$".toRegex() // https://www.rfc-editor.org/rfc/rfc4648.html#section-4
private val rfcBase32Alphabet get() = "^[2-7A-Z]+$".toRegex() // https://www.rfc-editor.org/rfc/rfc4648.html#section-6

private val rfcBase64Alphabet get() = "^[0-9A-Za-z+/=]+$".toRegex() // https://www.rfc-editor.org/rfc/rfc4648.html#section-4
private val rfcBase32Alphabet get() = "^[2-7A-Z=]+$".toRegex() // https://www.rfc-editor.org/rfc/rfc4648.html#section-6
private val rfcBase16Alphabet get() = "^[0-9A-F]+$".toRegex() // https://www.rfc-editor.org/rfc/rfc4648.html#section-8
private val base36Alphabet get() = "^[0-9A-Z]+$".toRegex()
private val base36Alphabet get() = "^[0-9A-Z=]+$".toRegex()

open class StringValueFactory<DOMAIN : Value<String>>(
fn: (String) -> DOMAIN, validation: Validation<String>? = null,
Expand All @@ -51,7 +53,10 @@ open class Base64StringValueFactory<DOMAIN : Value<String>>(
validation: Validation<String> = { true },
parseFn: (String) -> String = { it },
showFn: (String) -> String = { it },
) : ValueFactory<DOMAIN, String>(fn, rfcBase64Alphabet::matches.and(validation), parseFn, showFn)
) : ValueFactory<DOMAIN, String>(fn, rfcBase64Alphabet::matches.and(validation), parseFn, showFn) {
private val encoder = Base64.getEncoder()
fun encode(value: ByteArray) = encoder.encodeToString(value).let(coerceFn)
}

open class Base36StringValueFactory<DOMAIN : Value<String>>(
fn: (String) -> DOMAIN,
Expand All @@ -72,7 +77,19 @@ open class Base16StringValueFactory<DOMAIN : Value<String>>(
validation: Validation<String> = { true },
parseFn: (String) -> String = { it },
showFn: (String) -> String = { it },
) : ValueFactory<DOMAIN, String>(fn, rfcBase16Alphabet::matches.and(validation), parseFn, showFn)
) : ValueFactory<DOMAIN, String>(fn, rfcBase16Alphabet::matches.and(validation), parseFn, showFn) {
// Source: https://stackoverflow.com/a/9855338/1253613
private val base16Chars = "0123456789ABCDEF".toCharArray()
fun encode(bytes: ByteArray): DOMAIN {
val hexChars = CharArray(bytes.size * 2)
for (j in bytes.indices) {
val v = bytes[j].toInt() and 0xFF
hexChars[j * 2] = this.base16Chars[v ushr 4]
hexChars[j * 2 + 1] = this.base16Chars[v and 0x0F]
}
return String(hexChars).let(coerceFn)
}
}

open class CharValueFactory<DOMAIN : Value<Char>>(
fn: (Char) -> DOMAIN, validation: Validation<Char>? = null
Expand Down
25 changes: 25 additions & 0 deletions values4k/src/test/kotlin/dev/forkhandles/values/MultiBaseTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,21 @@ class MultiBaseTest {
assertThat(MyBase64Value.parse("ABCDefgh123+/").value, equalTo("ABCDefgh123+/"))
}

@Test
fun `parse base64 - with padding`() {
assertThat(MyBase64Value.parse("Zm9vYmFyYmF6YmFuZw==").value, equalTo("Zm9vYmFyYmF6YmFuZw=="))
}

@Test
fun `parse base64 - invalid`() {
assertThat(MyBase64Value.parseOrNull("ABCD!!!"), absent())
}

@Test
fun `encode base64`() {
assertThat(MyBase64Value.encode("foobarbaz".encodeToByteArray()), equalTo(MyBase64Value.parse("Zm9vYmFyYmF6")))
}

@Test
fun `parse base36 - valid`() {
assertThat(MyBase36Value.parse("ABCD123").value, equalTo("ABCD123"))
Expand All @@ -53,6 +63,11 @@ class MultiBaseTest {
assertThat(MyBase32Value.parse("ABCD23").value, equalTo("ABCD23"))
}

@Test
fun `parse base32 - with padding`() {
assertThat(MyBase32Value.parse("MZXW6YTBOJRGC6Q=").value, equalTo("MZXW6YTBOJRGC6Q="))
}

@Test
fun `parse base32 - strict casing`() {
assertThat(MyBase32Value.parseOrNull("ABcd23"), absent())
Expand All @@ -73,8 +88,18 @@ class MultiBaseTest {
assertThat(Mybase16Value.parseOrNull("ABcd123"), absent())
}

@Test
fun `parse base16 - no padding allowed`() {
assertThat(Mybase16Value.parseOrNull("ABCD123="), absent())
}

@Test
fun `parse base16 - invalid`() {
assertThat(Mybase16Value.parseOrNull("ABYZ123"), absent())
}

@Test
fun `encode base16`() {
assertThat(Mybase16Value.encode("foobarbaz".encodeToByteArray()), equalTo(Mybase16Value.parse("666F6F62617262617A")))
}
}

0 comments on commit c4d1c71

Please sign in to comment.