Refactor randomized password generator into a separate module (#1663)
This commit is contained in:
parent
c1ef2e7341
commit
abc62c2b6b
14 changed files with 126 additions and 92 deletions
|
@ -79,6 +79,7 @@ dependencies {
|
||||||
implementation(projects.formatCommon)
|
implementation(projects.formatCommon)
|
||||||
implementation(projects.openpgpKtx)
|
implementation(projects.openpgpKtx)
|
||||||
implementation(projects.passgen.diceware)
|
implementation(projects.passgen.diceware)
|
||||||
|
implementation(projects.passgen.random)
|
||||||
implementation(libs.androidx.activity.ktx)
|
implementation(libs.androidx.activity.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.androidx.autofill)
|
implementation(libs.androidx.autofill)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.setFragmentResult
|
import androidx.fragment.app.setFragmentResult
|
||||||
|
@ -23,11 +24,12 @@ import com.github.michaelbull.result.runCatching
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dev.msfjarvis.aps.R
|
import dev.msfjarvis.aps.R
|
||||||
import dev.msfjarvis.aps.databinding.FragmentPwgenBinding
|
import dev.msfjarvis.aps.databinding.FragmentPwgenBinding
|
||||||
|
import dev.msfjarvis.aps.passgen.random.MaxIterationsExceededException
|
||||||
|
import dev.msfjarvis.aps.passgen.random.NoCharactersIncludedException
|
||||||
|
import dev.msfjarvis.aps.passgen.random.PasswordGenerator
|
||||||
|
import dev.msfjarvis.aps.passgen.random.PasswordLengthTooShortException
|
||||||
|
import dev.msfjarvis.aps.passgen.random.PasswordOption
|
||||||
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
|
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
|
||||||
import dev.msfjarvis.aps.util.pwgen.PasswordGenerator
|
|
||||||
import dev.msfjarvis.aps.util.pwgen.PasswordGenerator.generate
|
|
||||||
import dev.msfjarvis.aps.util.pwgen.PasswordGenerator.setPrefs
|
|
||||||
import dev.msfjarvis.aps.util.pwgen.PasswordOption
|
|
||||||
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -96,10 +98,23 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generate(passwordField: AppCompatTextView) {
|
private fun generate(passwordField: AppCompatTextView) {
|
||||||
setPreferences()
|
val passwordOptions = getSelectedOptions()
|
||||||
|
val passwordLength = getLength()
|
||||||
|
setPrefs(requireContext(), passwordOptions, passwordLength)
|
||||||
passwordField.text =
|
passwordField.text =
|
||||||
runCatching { generate(requireContext().applicationContext) }.getOrElse { e ->
|
runCatching { PasswordGenerator.generate(passwordOptions, passwordLength) }.getOrElse {
|
||||||
Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
|
exception ->
|
||||||
|
val errorText =
|
||||||
|
when (exception) {
|
||||||
|
is MaxIterationsExceededException ->
|
||||||
|
requireContext().getString(R.string.pwgen_max_iterations_exceeded)
|
||||||
|
is NoCharactersIncludedException ->
|
||||||
|
requireContext().getString(R.string.pwgen_no_chars_error)
|
||||||
|
is PasswordLengthTooShortException ->
|
||||||
|
requireContext().getString(R.string.pwgen_length_too_short_error)
|
||||||
|
else -> requireContext().getString(R.string.pwgen_some_error_occurred)
|
||||||
|
}
|
||||||
|
Toast.makeText(requireActivity(), errorText, Toast.LENGTH_SHORT).show()
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,18 +123,34 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
|
||||||
return requireDialog().findViewById<CheckBox>(id).isChecked
|
return requireDialog().findViewById<CheckBox>(id).isChecked
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setPreferences() {
|
private fun getSelectedOptions(): List<PasswordOption> {
|
||||||
val preferences =
|
return listOfNotNull(
|
||||||
listOfNotNull(
|
PasswordOption.NoDigits.takeIf { !isChecked(R.id.numerals) },
|
||||||
PasswordOption.NoDigits.takeIf { !isChecked(R.id.numerals) },
|
PasswordOption.AtLeastOneSymbol.takeIf { isChecked(R.id.symbols) },
|
||||||
PasswordOption.AtLeastOneSymbol.takeIf { isChecked(R.id.symbols) },
|
PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
|
||||||
PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
|
PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
|
||||||
PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
|
PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
|
||||||
PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
|
PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) }
|
||||||
PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) }
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
private fun getLength(): Int {
|
||||||
val lengthText = requireDialog().findViewById<EditText>(R.id.lengthNumber).text.toString()
|
val lengthText = requireDialog().findViewById<EditText>(R.id.lengthNumber).text.toString()
|
||||||
val length = lengthText.toIntOrNull()?.takeIf { it >= 0 } ?: PasswordGenerator.DEFAULT_LENGTH
|
return lengthText.toIntOrNull()?.takeIf { it >= 0 } ?: PasswordGenerator.DEFAULT_LENGTH
|
||||||
setPrefs(requireActivity().applicationContext, preferences, length)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the [PasswordOption]s in [options] and sets [targetLength] as the length for generated
|
||||||
|
* passwords.
|
||||||
|
*/
|
||||||
|
private fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
|
||||||
|
ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
|
||||||
|
for (possibleOption in PasswordOption.values()) putBoolean(
|
||||||
|
possibleOption.key,
|
||||||
|
possibleOption in options
|
||||||
|
)
|
||||||
|
putInt("length", targetLength)
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -391,5 +391,6 @@
|
||||||
<string name="pgp_key_import_succeeded">Successfully imported PGP key</string>
|
<string name="pgp_key_import_succeeded">Successfully imported PGP key</string>
|
||||||
<string name="pgp_key_import_succeeded_message">The key ID of the imported key is given below, please review it for correctness:\n%1$s</string>
|
<string name="pgp_key_import_succeeded_message">The key ID of the imported key is given below, please review it for correctness:\n%1$s</string>
|
||||||
<string name="pref_category_pgp_title">PGP settings</string>
|
<string name="pref_category_pgp_title">PGP settings</string>
|
||||||
|
<string name="pwgen_some_error_occurred">Some error occurred</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -15,5 +15,6 @@ apiValidation {
|
||||||
"crypto-pgpainless",
|
"crypto-pgpainless",
|
||||||
"format-common",
|
"format-common",
|
||||||
"diceware",
|
"diceware",
|
||||||
|
"random",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
1
passgen/random/.gitignore
vendored
Normal file
1
passgen/random/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
4
passgen/random/build.gradle.kts
Normal file
4
passgen/random/build.gradle.kts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
id("com.github.android-password-store.kotlin-library")
|
||||||
|
}
|
|
@ -2,56 +2,28 @@
|
||||||
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
package dev.msfjarvis.aps.util.pwgen
|
package dev.msfjarvis.aps.passgen.random
|
||||||
|
|
||||||
import android.content.Context
|
import dev.msfjarvis.aps.passgen.random.util.clearFlag
|
||||||
import androidx.core.content.edit
|
import dev.msfjarvis.aps.passgen.random.util.hasFlag
|
||||||
import dev.msfjarvis.aps.R
|
|
||||||
import dev.msfjarvis.aps.util.extensions.clearFlag
|
|
||||||
import dev.msfjarvis.aps.util.extensions.hasFlag
|
|
||||||
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
|
||||||
|
|
||||||
enum class PasswordOption(val key: String) {
|
public object PasswordGenerator {
|
||||||
NoDigits("0"),
|
|
||||||
NoUppercaseLetters("A"),
|
|
||||||
NoAmbiguousCharacters("B"),
|
|
||||||
FullyRandom("s"),
|
|
||||||
AtLeastOneSymbol("y"),
|
|
||||||
NoLowercaseLetters("L")
|
|
||||||
}
|
|
||||||
|
|
||||||
object PasswordGenerator {
|
public const val DEFAULT_LENGTH: Int = 16
|
||||||
|
|
||||||
const val DEFAULT_LENGTH = 16
|
internal const val DIGITS = 0x0001
|
||||||
|
internal const val UPPERS = 0x0002
|
||||||
|
internal const val SYMBOLS = 0x0004
|
||||||
|
internal const val NO_AMBIGUOUS = 0x0008
|
||||||
|
internal const val LOWERS = 0x0020
|
||||||
|
|
||||||
const val DIGITS = 0x0001
|
internal const val DIGITS_STR = "0123456789"
|
||||||
const val UPPERS = 0x0002
|
internal const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
const val SYMBOLS = 0x0004
|
internal const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
|
||||||
const val NO_AMBIGUOUS = 0x0008
|
internal const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||||
const val LOWERS = 0x0020
|
internal const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
|
||||||
|
|
||||||
const val DIGITS_STR = "0123456789"
|
internal fun isValidPassword(password: String, pwFlags: Int): Boolean {
|
||||||
const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
|
|
||||||
const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
|
||||||
const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables the [PasswordOption] s in [options] and sets [targetLength] as the length for generated
|
|
||||||
* passwords.
|
|
||||||
*/
|
|
||||||
fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
|
|
||||||
ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
|
|
||||||
for (possibleOption in PasswordOption.values()) putBoolean(
|
|
||||||
possibleOption.key,
|
|
||||||
possibleOption in options
|
|
||||||
)
|
|
||||||
putInt("length", targetLength)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isValidPassword(password: String, pwFlags: Int): Boolean {
|
|
||||||
if (pwFlags hasFlag DIGITS && password.none { it in DIGITS_STR }) return false
|
if (pwFlags hasFlag DIGITS && password.none { it in DIGITS_STR }) return false
|
||||||
if (pwFlags hasFlag UPPERS && password.none { it in UPPERS_STR }) return false
|
if (pwFlags hasFlag UPPERS && password.none { it in UPPERS_STR }) return false
|
||||||
if (pwFlags hasFlag LOWERS && password.none { it in LOWERS_STR }) return false
|
if (pwFlags hasFlag LOWERS && password.none { it in LOWERS_STR }) return false
|
||||||
|
@ -60,17 +32,15 @@ object PasswordGenerator {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generates a password using the preferences set by [setPrefs]. */
|
/** Generates a password using the given [passwordOptions] and [length]. */
|
||||||
@Throws(PasswordGeneratorException::class)
|
@Throws(PasswordGeneratorException::class)
|
||||||
fun generate(ctx: Context): String {
|
public fun generate(passwordOptions: List<PasswordOption>, length: Int = DEFAULT_LENGTH): String {
|
||||||
val prefs = ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
|
|
||||||
var numCharacterCategories = 0
|
var numCharacterCategories = 0
|
||||||
|
|
||||||
var phonemes = true
|
var phonemes = true
|
||||||
var pwgenFlags = DIGITS or UPPERS or LOWERS
|
var pwgenFlags = DIGITS or UPPERS or LOWERS
|
||||||
|
|
||||||
for (option in PasswordOption.values()) {
|
for (option in PasswordOption.values()) {
|
||||||
if (prefs.getBoolean(option.key, false)) {
|
if (option in passwordOptions) {
|
||||||
when (option) {
|
when (option) {
|
||||||
PasswordOption.NoDigits -> pwgenFlags = pwgenFlags.clearFlag(DIGITS)
|
PasswordOption.NoDigits -> pwgenFlags = pwgenFlags.clearFlag(DIGITS)
|
||||||
PasswordOption.NoUppercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(UPPERS)
|
PasswordOption.NoUppercaseLetters -> pwgenFlags = pwgenFlags.clearFlag(UPPERS)
|
||||||
|
@ -98,14 +68,11 @@ object PasswordGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val length = prefs.getInt(PreferenceKeys.LENGTH, DEFAULT_LENGTH)
|
|
||||||
if (pwgenFlags.clearFlag(NO_AMBIGUOUS) == 0) {
|
if (pwgenFlags.clearFlag(NO_AMBIGUOUS) == 0) {
|
||||||
throw PasswordGeneratorException(ctx.resources.getString(R.string.pwgen_no_chars_error))
|
throw NoCharactersIncludedException()
|
||||||
}
|
}
|
||||||
if (length < numCharacterCategories) {
|
if (length < numCharacterCategories) {
|
||||||
throw PasswordGeneratorException(
|
throw PasswordLengthTooShortException()
|
||||||
ctx.resources.getString(R.string.pwgen_length_too_short_error)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (!(pwgenFlags hasFlag UPPERS) && !(pwgenFlags hasFlag LOWERS)) {
|
if (!(pwgenFlags hasFlag UPPERS) && !(pwgenFlags hasFlag LOWERS)) {
|
||||||
phonemes = false
|
phonemes = false
|
||||||
|
@ -120,10 +87,7 @@ object PasswordGenerator {
|
||||||
var password: String?
|
var password: String?
|
||||||
var iterations = 0
|
var iterations = 0
|
||||||
do {
|
do {
|
||||||
if (iterations++ > 1000)
|
if (iterations++ > 1000) throw MaxIterationsExceededException()
|
||||||
throw PasswordGeneratorException(
|
|
||||||
ctx.resources.getString(R.string.pwgen_max_iterations_exceeded)
|
|
||||||
)
|
|
||||||
password =
|
password =
|
||||||
if (phonemes) {
|
if (phonemes) {
|
||||||
RandomPhonemesGenerator.generate(length, pwgenFlags)
|
RandomPhonemesGenerator.generate(length, pwgenFlags)
|
||||||
|
@ -133,6 +97,4 @@ object PasswordGenerator {
|
||||||
} while (password == null)
|
} while (password == null)
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasswordGeneratorException(string: String) : Exception(string)
|
|
||||||
}
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.msfjarvis.aps.passgen.random
|
||||||
|
|
||||||
|
public sealed class PasswordGeneratorException(message: String? = null, cause: Throwable? = null) :
|
||||||
|
Throwable(message, cause)
|
||||||
|
|
||||||
|
public class MaxIterationsExceededException : PasswordGeneratorException()
|
||||||
|
|
||||||
|
public class NoCharactersIncludedException : PasswordGeneratorException()
|
||||||
|
|
||||||
|
public class PasswordLengthTooShortException : PasswordGeneratorException()
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.msfjarvis.aps.passgen.random
|
||||||
|
|
||||||
|
public enum class PasswordOption(public val key: String) {
|
||||||
|
NoDigits("0"),
|
||||||
|
NoUppercaseLetters("A"),
|
||||||
|
NoAmbiguousCharacters("B"),
|
||||||
|
FullyRandom("s"),
|
||||||
|
AtLeastOneSymbol("y"),
|
||||||
|
NoLowercaseLetters("L")
|
||||||
|
}
|
|
@ -2,30 +2,30 @@
|
||||||
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
package dev.msfjarvis.aps.util.pwgen
|
package dev.msfjarvis.aps.passgen.random
|
||||||
|
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
private val secureRandom = SecureRandom()
|
private val secureRandom = SecureRandom()
|
||||||
|
|
||||||
/** Returns a number between 0 (inclusive) and [exclusiveBound](exclusive). */
|
/** Returns a number between 0 (inclusive) and [exclusiveBound](exclusive). */
|
||||||
fun secureRandomNumber(exclusiveBound: Int) = secureRandom.nextInt(exclusiveBound)
|
internal fun secureRandomNumber(exclusiveBound: Int) = secureRandom.nextInt(exclusiveBound)
|
||||||
|
|
||||||
/** Returns `true` and `false` with probablity 50% each. */
|
/** Returns `true` and `false` with probablity 50% each. */
|
||||||
fun secureRandomBoolean() = secureRandom.nextBoolean()
|
internal fun secureRandomBoolean() = secureRandom.nextBoolean()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `true` with probability [percentTrue]% and `false` with probability `(100 - [percentTrue]
|
* Returns `true` with probability [percentTrue]% and `false` with probability `(100 - [percentTrue]
|
||||||
* )`%.
|
* )`%.
|
||||||
*/
|
*/
|
||||||
fun secureRandomBiasedBoolean(percentTrue: Int): Boolean {
|
internal fun secureRandomBiasedBoolean(percentTrue: Int): Boolean {
|
||||||
require(1 <= percentTrue) { "Probability for returning `true` must be at least 1%" }
|
require(1 <= percentTrue) { "Probability for returning `true` must be at least 1%" }
|
||||||
require(percentTrue <= 99) { "Probability for returning `true` must be at most 99%" }
|
require(percentTrue <= 99) { "Probability for returning `true` must be at most 99%" }
|
||||||
return secureRandomNumber(100) < percentTrue
|
return secureRandomNumber(100) < percentTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Array<T>.secureRandomElement() = this[secureRandomNumber(size)]
|
internal fun <T> Array<T>.secureRandomElement() = this[secureRandomNumber(size)]
|
||||||
|
|
||||||
fun <T> List<T>.secureRandomElement() = this[secureRandomNumber(size)]
|
internal fun <T> List<T>.secureRandomElement() = this[secureRandomNumber(size)]
|
||||||
|
|
||||||
fun String.secureRandomCharacter() = this[secureRandomNumber(length)]
|
internal fun String.secureRandomCharacter() = this[secureRandomNumber(length)]
|
|
@ -2,11 +2,11 @@
|
||||||
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
package dev.msfjarvis.aps.util.pwgen
|
package dev.msfjarvis.aps.passgen.random
|
||||||
|
|
||||||
import dev.msfjarvis.aps.util.extensions.hasFlag
|
import dev.msfjarvis.aps.passgen.random.util.hasFlag
|
||||||
|
|
||||||
object RandomPasswordGenerator {
|
internal object RandomPasswordGenerator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a random password of length [targetLength], taking the following flags in [pwFlags]
|
* Generates a random password of length [targetLength], taking the following flags in [pwFlags]
|
|
@ -2,12 +2,12 @@
|
||||||
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
package dev.msfjarvis.aps.util.pwgen
|
package dev.msfjarvis.aps.passgen.random
|
||||||
|
|
||||||
import dev.msfjarvis.aps.util.extensions.hasFlag
|
import dev.msfjarvis.aps.passgen.random.util.hasFlag
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
object RandomPhonemesGenerator {
|
internal object RandomPhonemesGenerator {
|
||||||
|
|
||||||
private const val CONSONANT = 0x0001
|
private const val CONSONANT = 0x0001
|
||||||
private const val VOWEL = 0x0002
|
private const val VOWEL = 0x0002
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dev.msfjarvis.aps.passgen.random.util
|
||||||
|
|
||||||
|
/** Clears the given [flag] from the value of this [Int] */
|
||||||
|
internal infix fun Int.clearFlag(flag: Int): Int {
|
||||||
|
return this and flag.inv()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks if this [Int] contains the given [flag] */
|
||||||
|
internal infix fun Int.hasFlag(flag: Int): Boolean {
|
||||||
|
return this and flag == flag
|
||||||
|
}
|
|
@ -53,3 +53,5 @@ include("format-common")
|
||||||
include("openpgp-ktx")
|
include("openpgp-ktx")
|
||||||
|
|
||||||
include("passgen:diceware")
|
include("passgen:diceware")
|
||||||
|
|
||||||
|
include(":passgen:random")
|
||||||
|
|
Loading…
Reference in a new issue