setCounter($counter); } public static function create(?string $secret = null, int $counter = 0, string $digest = 'sha1', int $digits = 6): HOTPInterface { return new self($secret, $counter, $digest, $digits); } protected function setCounter(int $counter): void { $this->setParameter('counter', $counter); } public function getCounter(): int { return $this->getParameter('counter'); } private function updateCounter(int $counter): void { $this->setCounter($counter); } public function getProvisioningUri(): string { return $this->generateURI('hotp', ['counter' => $this->getCounter()]); } /** * If the counter is not provided, the OTP is verified at the actual counter. */ public function verify(string $otp, ?int $counter = null, ?int $window = null): bool { Assertion::greaterOrEqualThan($counter, 0, 'The counter must be at least 0.'); if (null === $counter) { $counter = $this->getCounter(); } elseif ($counter < $this->getCounter()) { return false; } return $this->verifyOtpWithWindow($otp, $counter, $window); } private function getWindow(?int $window): int { return abs($window ?? 0); } private function verifyOtpWithWindow(string $otp, int $counter, ?int $window): bool { $window = $this->getWindow($window); for ($i = $counter; $i <= $counter + $window; ++$i) { if ($this->compareOTP($this->at($i), $otp)) { $this->updateCounter($i + 1); return true; } } return false; } /** * @return array */ protected function getParameterMap(): array { $v = array_merge( parent::getParameterMap(), ['counter' => function ($value): int { Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.'); return (int) $value; }] ); return $v; } }