{"record":{"id":"7c068fac0bbdefd0","repo":"ellite/Wallos","slug":"invalid-data","errorCode":null,"errorMessage":"Invalid data.","messagePattern":"Invalid data\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"libs/OTPHP/OTP.php","lineNumber":65,"sourceCode":"     * @return non-empty-string\n     */\n    final protected static function generateSecret(): string\n    {\n        return Base32::encodeUpper(random_bytes(self::DEFAULT_SECRET_SIZE));\n    }\n\n    /**\n     * The OTP at the specified input.\n     *\n     * @param 0|positive-int $input\n     *\n     * @return non-empty-string\n     */\n    protected function generateOTP(int $input): string\n    {\n        $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret(), true);\n        $unpacked = unpack('C*', $hash);\n        $unpacked !== false || throw new InvalidArgumentException('Invalid data.');\n        $hmac = array_values($unpacked);\n\n        $offset = ($hmac[count($hmac) - 1] & 0xF);\n        $code = ($hmac[$offset] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);\n        $otp = $code % (10 ** $this->getDigits());\n\n        return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT);\n    }\n\n    /**\n     * @param array<non-empty-string, mixed> $options\n     */\n    protected function filterOptions(array &$options): void\n    {\n        foreach ([\n            'algorithm' => 'sha1',\n            'period' => 30,\n            'digits' => 6,","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/ellite/Wallos/blob/52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd/libs/OTPHP/OTP.php#L47-L83","documentation":"OTP::generateOTP calls unpack('C*', $hash) to convert the HMAC bytes to a byte array and throws InvalidArgumentException if unpack returns false — which for a valid hash string essentially cannot happen, so this is a defensive internal invariant check. It fires during at()/code generation.","triggerScenarios":"unpack() failing on the HMAC hash — only realistically possible if hash_hmac returned an empty/invalid string, e.g. an unknown digest algorithm name or an empty decoded secret slipping through.","commonSituations":"Setting an unsupported digest via setDigest (typo like 'sha257'), or a PHP environment where hash_hmac is disabled/behaving unexpectedly.","solutions":["Verify the digest is one of sha1, sha256, sha512: $otp->setDigest('sha256').","Ensure the secret is a valid non-empty base32 string so getDecodedSecret() yields bytes.","Check the PHP hash extension is enabled (php -m | grep hash).","If it persists, report as a library bug — unpack on a valid hash_hmac result should never fail."],"exampleFix":"// before\n$totp->setDigest('sha512-256'); // unsupported\n// after\n$totp->setDigest('sha512');","handlingStrategy":"validation","validationCode":"if (!in_array($otp->getDigest(), ['sha1', 'sha256', 'sha512'], true)) {\n    throw new DomainException('Unsupported digest for OTP generation');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $code = $totp->now();\n} catch (InvalidArgumentException | RuntimeException $e) {\n    log_critical('OTP generation failed', ['exception' => $e->getMessage()]);\n    throw new OtpGenerationException($e);\n}","preventionTips":["Restrict digest values to sha1/sha256/sha512 via allow-list","Ensure secrets are valid base32 so the HMAC input is well-formed","Keep the PHP hash extension enabled and tested in CI","Treat this exception as a bug signal, not user error — alert on it"],"tags":["php","otp","hmac","internal"],"backgroundTag":"internal-invariant-violation","analyzedSha":"52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd","analyzedAt":"2026-09-13T14:09:30.873Z","contentChangedAt":"2026-09-13T14:09:30.873Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}