- Fix RedisCache driver to handle MGET failures gracefully with fallback - Add comprehensive discovery context comparison debug tools - Identify root cause: WEB context discovery missing 166 items vs CLI - WEB context missing RequestFactory class entirely (52 vs 69 commands) - Improved exception handling with detailed binding diagnostics
152 lines
3.7 KiB
PHP
152 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Core\Encoding;
|
|
|
|
/**
|
|
* Base32 Alphabet Enum
|
|
*
|
|
* Defines different Base32 alphabets for various use cases:
|
|
* - RFC3548: Standard Base32 alphabet for TOTP and general use
|
|
* - CROCKFORD: Crockford's Base32 for ULIDs and human-readable IDs
|
|
*/
|
|
enum Base32Alphabet: string
|
|
{
|
|
case RFC3548 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
case CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
|
|
/**
|
|
* Get the alphabet string
|
|
*/
|
|
public function getAlphabet(): string
|
|
{
|
|
return $this->value;
|
|
}
|
|
|
|
/**
|
|
* Check if this alphabet uses padding
|
|
*/
|
|
public function usesPadding(): bool
|
|
{
|
|
return match ($this) {
|
|
self::RFC3548 => true,
|
|
self::CROCKFORD => false
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the character count (always 32 for Base32)
|
|
*/
|
|
public function getCharacterCount(): int
|
|
{
|
|
return 32;
|
|
}
|
|
|
|
/**
|
|
* Validate if a character exists in this alphabet
|
|
*/
|
|
public function containsCharacter(string $char): bool
|
|
{
|
|
return str_contains($this->value, strtoupper($char));
|
|
}
|
|
|
|
/**
|
|
* Get the index of a character in the alphabet
|
|
*/
|
|
public function getCharacterIndex(string $char): int
|
|
{
|
|
$index = strpos($this->value, strtoupper($char));
|
|
|
|
if ($index === false) {
|
|
throw new \InvalidArgumentException("Character '{$char}' not found in {$this->name} alphabet");
|
|
}
|
|
|
|
return $index;
|
|
}
|
|
|
|
/**
|
|
* Get character at specific index
|
|
*/
|
|
public function getCharacterAt(int $index): string
|
|
{
|
|
if ($index < 0 || $index >= 32) {
|
|
throw new \InvalidArgumentException("Index must be between 0 and 31, got {$index}");
|
|
}
|
|
|
|
return $this->value[$index];
|
|
}
|
|
|
|
/**
|
|
* Validate an encoded string against this alphabet
|
|
*/
|
|
public function isValidEncoded(string $encoded): bool
|
|
{
|
|
// Remove padding if this alphabet uses it
|
|
if ($this->usesPadding()) {
|
|
$encoded = rtrim($encoded, '=');
|
|
}
|
|
|
|
$encoded = strtoupper($encoded);
|
|
|
|
// Check if all characters are valid
|
|
for ($i = 0, $len = strlen($encoded); $i < $len; $i++) {
|
|
if (! $this->containsCharacter($encoded[$i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get recommended use cases for this alphabet
|
|
* @return array<int, string>
|
|
*/
|
|
public function getUseCases(): array
|
|
{
|
|
return match ($this) {
|
|
self::RFC3548 => [
|
|
'TOTP secrets',
|
|
'General Base32 encoding',
|
|
'Email verification codes',
|
|
'API tokens',
|
|
],
|
|
self::CROCKFORD => [
|
|
'ULIDs',
|
|
'Human-readable identifiers',
|
|
'Short URLs',
|
|
'Database primary keys',
|
|
]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get description of this alphabet
|
|
*/
|
|
public function getDescription(): string
|
|
{
|
|
return match ($this) {
|
|
self::RFC3548 => 'RFC 3548 standard Base32 alphabet with padding',
|
|
self::CROCKFORD => 'Crockford\'s Base32 alphabet without padding, optimized for human readability'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate a random string using this alphabet
|
|
*/
|
|
public function generateRandom(int $length): string
|
|
{
|
|
if ($length < 1) {
|
|
throw new \InvalidArgumentException('Length must be positive');
|
|
}
|
|
|
|
$result = '';
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$result .= $this->value[random_int(0, 31)];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|