Initial commit: hotpcalc CLI
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package otp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func exampleConfig(t *testing.T) Config {
|
||||
t.Helper()
|
||||
s, err := ParseSeed(exampleSeed, FormatAuto)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseSeed: %v", err)
|
||||
}
|
||||
return Config{Secret: s.Secret, Digits: 6, Alg: SHA1, Step: DefaultStep}
|
||||
}
|
||||
|
||||
func TestFindCounter_RoundTrip(t *testing.T) {
|
||||
c := exampleConfig(t)
|
||||
const want = uint64(41)
|
||||
|
||||
m, err := FindCounter(c, c.HOTP(want), c.HOTP(want+1), 0, 100000)
|
||||
if err != nil {
|
||||
t.Fatalf("FindCounter: %v", err)
|
||||
}
|
||||
if len(m) != 1 {
|
||||
t.Fatalf("найдено %d совпадений, ожидалось 1: %+v", len(m), m)
|
||||
}
|
||||
if m[0].Counter != want {
|
||||
t.Errorf("счётчик = %d, ожидалось %d", m[0].Counter, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCounter_RFCVectors(t *testing.T) {
|
||||
// Пара 287082/359152 из RFC 4226 — это счётчики 1 и 2.
|
||||
c := Config{Secret: rfc4226Key, Digits: 6, Alg: SHA1, Step: DefaultStep}
|
||||
m, err := FindCounter(c, "287082", "359152", 0, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("FindCounter: %v", err)
|
||||
}
|
||||
if m[0].Counter != 1 {
|
||||
t.Errorf("счётчик = %d, ожидалось 1", m[0].Counter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCounter_NotFound(t *testing.T) {
|
||||
c := exampleConfig(t)
|
||||
_, err := FindCounter(c, "000000", "000000", 0, 1000)
|
||||
var nf *ErrNotFound
|
||||
if !errors.As(err, &nf) {
|
||||
t.Fatalf("ожидалась ErrNotFound, получено %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCounter_OutOfRange(t *testing.T) {
|
||||
c := exampleConfig(t)
|
||||
// Позиция 500 лежит вне диапазона [0, 100).
|
||||
if _, err := FindCounter(c, c.HOTP(500), c.HOTP(501), 0, 100); err == nil {
|
||||
t.Error("ожидалась ошибка: позиция вне диапазона перебора")
|
||||
}
|
||||
// С правильным смещением находится.
|
||||
m, err := FindCounter(c, c.HOTP(500), c.HOTP(501), 450, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("FindCounter со смещением: %v", err)
|
||||
}
|
||||
if m[0].Counter != 500 {
|
||||
t.Errorf("счётчик = %d, ожидалось 500", m[0].Counter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCounter_SpanTooSmall(t *testing.T) {
|
||||
c := exampleConfig(t)
|
||||
if _, err := FindCounter(c, "123456", "654321", 0, 1); err == nil {
|
||||
t.Error("span < 2 должен отвергаться")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCounter_ReversedOrderNotMatched(t *testing.T) {
|
||||
c := exampleConfig(t)
|
||||
// Коды, поданные в обратном порядке, не должны находиться.
|
||||
if _, err := FindCounter(c, c.HOTP(11), c.HOTP(10), 0, 10000); err == nil {
|
||||
t.Error("перепутанный порядок кодов не должен давать совпадение")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTimeStep_RoundTrip(t *testing.T) {
|
||||
c := exampleConfig(t)
|
||||
now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Токен «спешит» на 2 минуты: коды сняты на 4 шага вперёд.
|
||||
step := c.TimeStep(now) + 4
|
||||
m, err := FindTimeStep(c, c.HOTP(step), c.HOTP(step+1), now, time.Hour)
|
||||
if err != nil {
|
||||
t.Fatalf("FindTimeStep: %v", err)
|
||||
}
|
||||
if len(m) != 1 {
|
||||
t.Fatalf("найдено %d совпадений, ожидалось 1", len(m))
|
||||
}
|
||||
if m[0].Counter != step {
|
||||
t.Errorf("шаг = %d, ожидалось %d", m[0].Counter, step)
|
||||
}
|
||||
if want := c.StepStart(step); !m[0].Time.Equal(want) {
|
||||
t.Errorf("время = %v, ожидалось %v", m[0].Time, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTimeStep_Step60(t *testing.T) {
|
||||
s, _ := ParseSeed(exampleSeed, FormatAuto)
|
||||
c := Config{Secret: s.Secret, Digits: 6, Alg: SHA1, Step: 60 * time.Second}
|
||||
now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
step := c.TimeStep(now) - 10
|
||||
m, err := FindTimeStep(c, c.HOTP(step), c.HOTP(step+1), now, 24*time.Hour)
|
||||
if err != nil {
|
||||
t.Fatalf("FindTimeStep: %v", err)
|
||||
}
|
||||
if m[0].Counter != step {
|
||||
t.Errorf("шаг = %d, ожидалось %d", m[0].Counter, step)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindTimeStep_OutsideWindow(t *testing.T) {
|
||||
c := exampleConfig(t)
|
||||
now := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
step := c.TimeStep(now) + 10000
|
||||
if _, err := FindTimeStep(c, c.HOTP(step), c.HOTP(step+1), now, time.Minute); err == nil {
|
||||
t.Error("ожидалась ошибка: позиция вне окна поиска")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFindCounter(b *testing.B) {
|
||||
s, _ := ParseSeed(exampleSeed, FormatAuto)
|
||||
c := Config{Secret: s.Secret, Digits: 6, Alg: SHA1, Step: DefaultStep}
|
||||
o1, o2 := c.HOTP(999998), c.HOTP(999999)
|
||||
for b.Loop() {
|
||||
if _, err := FindCounter(c, o1, o2, 0, 1000000); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user