From 16392b75814a71a4d78b3598cd3364baeb0b446e Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 9 Dec 2021 23:23:46 +0530 Subject: [PATCH] diceware: add Die class and tests --- passgen/diceware/api/diceware.api | 10 ++++ .../dev/msfjarvis/aps/passgen/diceware/Die.kt | 30 ++++++++++++ .../passgen/diceware/RandomIntGenerator.kt | 15 ++++++ .../msfjarvis/aps/passgen/diceware/DieTest.kt | 47 +++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 passgen/diceware/api/diceware.api create mode 100644 passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/Die.kt create mode 100644 passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/RandomIntGenerator.kt create mode 100644 passgen/diceware/src/test/kotlin/dev/msfjarvis/aps/passgen/diceware/DieTest.kt diff --git a/passgen/diceware/api/diceware.api b/passgen/diceware/api/diceware.api new file mode 100644 index 00000000..8d16ae8f --- /dev/null +++ b/passgen/diceware/api/diceware.api @@ -0,0 +1,10 @@ +public final class dev/msfjarvis/aps/passgen/diceware/Die { + public fun (ILdev/msfjarvis/aps/passgen/diceware/RandomIntGenerator;)V + public final fun roll ()I + public final fun rollMultiple (I)I +} + +public abstract interface class dev/msfjarvis/aps/passgen/diceware/RandomIntGenerator { + public abstract fun get (Lkotlin/ranges/IntRange;)I +} + diff --git a/passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/Die.kt b/passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/Die.kt new file mode 100644 index 00000000..d6e5aa23 --- /dev/null +++ b/passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/Die.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.passgen.diceware + +import javax.inject.Inject + +/** Basic implementation of a die with configurable number of sides. */ +public class Die +@Inject +constructor( + private val sides: Int, + private val random: RandomIntGenerator, +) { + + /** Roll the die to return a single number. */ + public fun roll(): Int { + return random.get(1..sides) + } + + /** + * Roll the die multiple times, concatenating each result to obtain a number with [iterations] + * digits. + */ + public fun rollMultiple(iterations: Int): Int { + return StringBuilder().apply { repeat(iterations) { append(roll()) } }.toString().toInt() + } +} diff --git a/passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/RandomIntGenerator.kt b/passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/RandomIntGenerator.kt new file mode 100644 index 00000000..7f957419 --- /dev/null +++ b/passgen/diceware/src/main/kotlin/dev/msfjarvis/aps/passgen/diceware/RandomIntGenerator.kt @@ -0,0 +1,15 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.passgen.diceware + +/** + * SAM interface that takes in an [IntRange] and returns a randomly chosen [Int] within its bounds. + * This is used as a replacement for [kotlin.random.Random] since there is no CSPRNG-backed + * implementation of it in the Kotlin stdlib. + */ +public fun interface RandomIntGenerator { + public fun get(range: IntRange): Int +} diff --git a/passgen/diceware/src/test/kotlin/dev/msfjarvis/aps/passgen/diceware/DieTest.kt b/passgen/diceware/src/test/kotlin/dev/msfjarvis/aps/passgen/diceware/DieTest.kt new file mode 100644 index 00000000..69a355c5 --- /dev/null +++ b/passgen/diceware/src/test/kotlin/dev/msfjarvis/aps/passgen/diceware/DieTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.passgen.diceware + +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertEquals + +class DieTest { + + /** Pre-seeded [Random] instance to ensure tests are deterministic. */ + private val random = Random(1_00_000) + + private val intGenerator = RandomIntGenerator { it.random(random) } + + @Test + fun test_one_roll() { + val die = Die(6, intGenerator) + assertEquals(5, die.roll()) + } + + @Test + fun test_multiple_rolls() { + val die = Die(6, intGenerator) + assertEquals(526242, die.rollMultiple(6)) + } + + @Test + fun test_consecutive_rolls() { + val die = Die(6, intGenerator) + assertEquals(5, die.roll()) + assertEquals(2, die.roll()) + assertEquals(6, die.roll()) + assertEquals(2, die.roll()) + assertEquals(4, die.roll()) + assertEquals(2, die.roll()) + } + + @Test + fun test_100_sides() { + val die = Die(100, intGenerator) + assertEquals(67, die.roll()) + } +}