-Output...
-
-Password # 1: "AAAA"
-Password # 2: "AAAB"
-Password # 3: "AABA"
-Password # 4: "AABB"
-Password # 5: "ABAA"
-Password # 6: "ABAB"
-Password # 7: "ABBA"
-Password # 8: "ABBB"
-Password # 9: "BAAA"
-Password # 10: "BAAB"
-Password # 11: "BABA"
-Password # 12: "BABB"
-Password # 13: "BBAA"
-Password # 14: "BBAB"
-Password # 15: "BBBA"
-Password # 16: "BBBB"
+Password # 9: "AAAAAAAI"
+Password # 10: "AAAAAAAJ"
@@ -170,27 +108,30 @@ goos: linux
goarch: amd64
pkg: github.com/jedib0t/go-passwords/passphrase
cpu: AMD Ryzen 9 5900X 12-Core Processor
-BenchmarkGenerator_Generate-12 4030634 292.0 ns/op 144 B/op 5 allocs/op
+BenchmarkGenerator_Generate-12 3979081 294.8 ns/op 144 B/op 5 allocs/op
PASS
-ok github.com/jedib0t/go-passwords/passphrase 1.603s
+ok github.com/jedib0t/go-passwords/passphrase 1.503s
goos: linux
goarch: amd64
pkg: github.com/jedib0t/go-passwords/password
cpu: AMD Ryzen 9 5900X 12-Core Processor
-BenchmarkGenerator_Generate-12 6263398 187.5 ns/op 40 B/op 2 allocs/op
+BenchmarkGenerator_Generate-12 5977402 199.4 ns/op 40 B/op 2 allocs/op
PASS
-ok github.com/jedib0t/go-passwords/password 1.375s
+ok github.com/jedib0t/go-passwords/password 1.414s
goos: linux
goarch: amd64
-pkg: github.com/jedib0t/go-passwords/password/sequencer
+pkg: github.com/jedib0t/go-passwords/odometer
cpu: AMD Ryzen 9 5900X 12-Core Processor
-BenchmarkSequencer_GotoN-12 4355002 274.6 ns/op 32 B/op 3 allocs/op
-BenchmarkSequencer_Next-12 13614666 88.99 ns/op 16 B/op 1 allocs/op
-BenchmarkSequencer_NextN-12 6216072 187.2 ns/op 32 B/op 3 allocs/op
-BenchmarkSequencer_Prev-12 13569340 87.69 ns/op 16 B/op 1 allocs/op
-BenchmarkSequencer_PrevN-12 4230654 277.9 ns/op 32 B/op 3 allocs/op
+BenchmarkOdometer_Decrement-12 56414820 21.25 ns/op 0 B/op 0 allocs/op
+BenchmarkOdometer_Decrement_Big-12 44742920 27.37 ns/op 0 B/op 0 allocs/op
+BenchmarkOdometer_DecrementN-12 6536234 177.3 ns/op 16 B/op 2 allocs/op
+BenchmarkOdometer_GotoLocation-12 5184144 220.7 ns/op 56 B/op 4 allocs/op
+BenchmarkOdometer_Increment-12 61866901 19.37 ns/op 0 B/op 0 allocs/op
+BenchmarkOdometer_Increment_Big-12 67560506 17.68 ns/op 0 B/op 0 allocs/op
+BenchmarkOdometer_IncrementN-12 7371675 172.7 ns/op 16 B/op 2 allocs/op
+BenchmarkOdometer_String-12 14852208 75.40 ns/op 16 B/op 1 allocs/op
PASS
-ok github.com/jedib0t/go-passwords/password/sequencer 6.888s
+ok github.com/jedib0t/go-passwords/odometer 10.282s
```
diff --git a/main.go b/main.go
index b536823..a6e2c52 100644
--- a/main.go
+++ b/main.go
@@ -1,36 +1,30 @@
package main
import (
- "context"
"fmt"
- "time"
"github.com/jedib0t/go-passwords/charset"
+ "github.com/jedib0t/go-passwords/odometer"
"github.com/jedib0t/go-passwords/passphrase"
"github.com/jedib0t/go-passwords/passphrase/dictionaries"
"github.com/jedib0t/go-passwords/password"
- "github.com/jedib0t/go-passwords/password/sequencer"
)
func main() {
fmt.Println("Passphrases:")
- passphraseGenerator()
+ demoPassphraseGenerator()
fmt.Println()
fmt.Println("Passwords:")
- passwordGenerator()
+ demoPasswordGenerator()
fmt.Println()
- fmt.Println("Passwords Sequenced:")
- passwordSequencer()
- fmt.Println()
-
- fmt.Println("Passwords Sequenced & Streamed:")
- passwordSequencerStreaming()
+ fmt.Println("Odometer:")
+ demoOdometer()
fmt.Println()
}
-func passphraseGenerator() {
+func demoPassphraseGenerator() {
g, err := passphrase.NewGenerator(
passphrase.WithCapitalizedWords(true),
passphrase.WithDictionary(dictionaries.English()),
@@ -47,7 +41,7 @@ func passphraseGenerator() {
}
}
-func passwordGenerator() {
+func demoPasswordGenerator() {
g, err := password.NewGenerator(
password.WithCharset(charset.AllChars.WithoutAmbiguity().WithoutDuplicates()),
password.WithLength(12),
@@ -63,55 +57,15 @@ func passwordGenerator() {
}
}
-func passwordSequencer() {
- s, err := sequencer.New(
- sequencer.WithCharset(charset.AllChars.WithoutAmbiguity()),
- sequencer.WithLength(8),
- )
- if err != nil {
- panic(err.Error())
- }
+func demoOdometer() {
+ o := odometer.New(charset.AlphabetsUpper, 8)
+
for idx := 1; idx <= 10; idx++ {
- fmt.Printf("Password #%3d: %#v\n", idx, s.Get())
+ fmt.Printf("Password #%3d: %#v\n", idx, o.String())
- if !s.HasNext() {
+ if o.AtEnd() {
break
}
- s.Next()
- }
-}
-
-func passwordSequencerStreaming() {
- s, err := sequencer.New(
- sequencer.WithCharset(charset.Charset("AB")),
- sequencer.WithLength(4),
- )
- if err != nil {
- panic(err.Error())
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
-
- chPasswords := make(chan string, 1)
- go func() {
- err := s.Stream(ctx, chPasswords)
- if err != nil {
- panic(err.Error())
- }
- }()
-
- idx := 0
- for {
- select {
- case <-ctx.Done():
- panic("timed out")
- case pw, ok := <-chPasswords:
- if !ok {
- return
- }
- idx++
- fmt.Printf("Password #%3d: %#v\n", idx, pw)
- }
+ o.Increment()
}
}
diff --git a/odometer/errors.go b/odometer/errors.go
new file mode 100644
index 0000000..7f1a9b8
--- /dev/null
+++ b/odometer/errors.go
@@ -0,0 +1,7 @@
+package odometer
+
+import "errors"
+
+var (
+ ErrInvalidLocation = errors.New("invalid location")
+)
diff --git a/odometer/odometer.go b/odometer/odometer.go
new file mode 100644
index 0000000..fc84140
--- /dev/null
+++ b/odometer/odometer.go
@@ -0,0 +1,262 @@
+package odometer
+
+import (
+ "math/big"
+ "slices"
+ "sync"
+
+ "github.com/jedib0t/go-passwords/charset"
+)
+
+var (
+ biZero = big.NewInt(0)
+ biOne = big.NewInt(1)
+)
+
+type Odometer interface {
+ AtEnd() bool
+ Decrement() bool
+ DecrementN(n *big.Int) bool
+ First()
+ GotoLocation(n *big.Int) error
+ Increment() bool
+ IncrementN(n *big.Int) bool
+ Last()
+ Location() *big.Int
+ String() string
+ Value() []int
+}
+
+type odometer struct {
+ base int
+ baseBigInt *big.Int
+ charset []rune
+ length int
+ location *big.Int
+ locationMax *big.Int
+ mutex sync.RWMutex
+ rollover bool
+ value []int
+ valueInCharset []rune
+}
+
+func New(cs charset.Charset, length int, opts ...Option) Odometer {
+ base := len(cs)
+ maxValues := numValues(base, length)
+
+ o := &odometer{
+ base: base,
+ baseBigInt: big.NewInt(int64(base)),
+ charset: []rune(cs),
+ length: length,
+ location: big.NewInt(1),
+ locationMax: new(big.Int).Set(maxValues),
+ value: make([]int, length),
+ valueInCharset: make([]rune, length),
+ }
+ for _, opt := range opts {
+ opt(o)
+ }
+ return o
+}
+
+func (o *odometer) AtEnd() bool {
+ o.mutex.RLock()
+ defer o.mutex.RUnlock()
+
+ return o.location.Cmp(o.locationMax) >= 0
+}
+
+func (o *odometer) Decrement() bool {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ // set the location
+ if o.location.Cmp(biOne) == 0 { // at first
+ if o.rollover {
+ o.last()
+ return true
+ }
+ return false
+ }
+
+ // decrement value
+ o.location.Sub(o.location, biOne)
+ for idx := o.length - 1; idx >= 0; idx-- {
+ if o.decrementAtIndex(idx) {
+ return true
+ }
+ }
+ return true
+}
+
+func (o *odometer) DecrementN(n *big.Int) bool {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ o.location.Sub(o.location, n)
+ if o.location.Cmp(biOne) < 0 { // less than min
+ if !o.rollover {
+ o.first()
+ return false
+ }
+ // move backwards from max; o.location is currently -ve --> so Add()
+ for o.location.Cmp(biOne) < 0 {
+ o.location.Add(o.locationMax, o.location)
+ }
+ }
+ o.computeValue()
+ return true
+}
+
+func (o *odometer) First() {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ o.first()
+}
+
+func (o *odometer) Location() *big.Int {
+ o.mutex.RLock()
+ defer o.mutex.RUnlock()
+
+ return new(big.Int).Set(o.location)
+}
+
+func (o *odometer) Increment() bool {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ // set the location
+ if o.location.Cmp(o.locationMax) == 0 { // at max
+ if o.rollover {
+ o.first()
+ return true
+ }
+ return false
+ }
+
+ // increment value
+ o.location.Add(o.location, biOne)
+ for idx := o.length - 1; idx >= 0; idx-- {
+ if o.incrementAtIndex(idx) {
+ return true
+ }
+ }
+ return true
+}
+
+func (o *odometer) IncrementN(n *big.Int) bool {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ o.location.Add(o.location, n)
+ if o.location.Cmp(o.locationMax) > 0 { // more than max
+ if !o.rollover {
+ o.last()
+ return false
+ }
+ // move forwards from zero
+ for o.location.Cmp(o.locationMax) > 0 {
+ o.location.Sub(o.location, o.locationMax)
+ }
+ }
+ o.computeValue()
+ return true
+}
+
+func (o *odometer) Last() {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ o.last()
+}
+
+func (o *odometer) GotoLocation(n *big.Int) error {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ if n.Cmp(biOne) < 0 || n.Cmp(o.locationMax) > 0 {
+ return ErrInvalidLocation
+ }
+ o.location.Set(n)
+ o.computeValue()
+ return nil
+}
+
+func (o *odometer) String() string {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ for idx := range o.valueInCharset {
+ o.valueInCharset[idx] = o.charset[o.value[idx]]
+ }
+ return string(o.valueInCharset)
+}
+
+func (o *odometer) Value() []int {
+ o.mutex.Lock()
+ defer o.mutex.Unlock()
+
+ return slices.Clone(o.value)
+}
+
+func (o *odometer) computeValue() {
+ // base conversion: convert the value of location to a decimal with the
+ // given base using continuous division and use all the remainders as the
+ // values
+
+ // prep the dividend, remainder and modulus
+ dividend, remainder := new(big.Int).Sub(o.location, biOne), new(big.Int)
+ // append values in reverse (from right to left)
+ valIdx := o.length - 1
+ // append every remainder until dividend becomes zero
+ for ; dividend.Cmp(biZero) != 0; valIdx-- {
+ dividend, remainder = dividend.QuoRem(dividend, o.baseBigInt, remainder)
+ o.value[valIdx] = int(remainder.Int64())
+ }
+ // left-pad the remaining characters with 0 (==> 0th char in charset)
+ for ; valIdx >= 0; valIdx-- {
+ o.value[valIdx] = 0
+ }
+}
+
+func (o *odometer) decrementAtIndex(idx int) bool {
+ if o.value[idx] > 0 {
+ o.value[idx]--
+ return true
+ }
+ if o.value[idx] == 0 && idx > 0 {
+ o.value[idx] = o.base - 1
+ o.decrementAtIndex(idx - 1)
+ return true
+ }
+ return false
+}
+
+func (o *odometer) first() {
+ o.location.Set(biOne)
+ for idx := range o.value {
+ o.value[idx] = 0
+ }
+}
+
+func (o *odometer) incrementAtIndex(idx int) bool {
+ if o.value[idx] < o.base-1 {
+ o.value[idx]++
+ return true
+ }
+ if o.value[idx] == o.base-1 && idx > 0 {
+ o.value[idx] = 0
+ o.incrementAtIndex(idx - 1)
+ return true
+ }
+ return false
+}
+
+func (o *odometer) last() {
+ o.location.Set(o.locationMax)
+ for idx := range o.value {
+ o.value[idx] = o.base - 1
+ }
+}
diff --git a/odometer/odometer_bench_test.go b/odometer/odometer_bench_test.go
new file mode 100644
index 0000000..579429f
--- /dev/null
+++ b/odometer/odometer_bench_test.go
@@ -0,0 +1,83 @@
+package odometer
+
+import (
+ "math"
+ "math/big"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/jedib0t/go-passwords/charset"
+ "github.com/stretchr/testify/assert"
+)
+
+func BenchmarkOdometer_Decrement(b *testing.B) {
+ o := New(charset.Numbers, 8, WithRolloverEnabled(true))
+
+ for i := 0; i < b.N; i++ {
+ _ = o.Decrement()
+ }
+}
+
+func BenchmarkOdometer_Decrement_Big(b *testing.B) {
+ o := New(charset.AllChars, 256)
+ o.Last()
+
+ for i := 0; i < b.N; i++ {
+ _ = o.Decrement()
+ }
+}
+
+func BenchmarkOdometer_DecrementN(b *testing.B) {
+ o := New(charset.Numbers, 8, WithRolloverEnabled(true))
+
+ n := big.NewInt(5)
+ for i := 0; i < b.N; i++ {
+ _ = o.DecrementN(n)
+ }
+}
+
+func BenchmarkOdometer_GotoLocation(b *testing.B) {
+ o := New(charset.Numbers, 8, WithRolloverEnabled(true))
+ maxValues := int64(math.Pow(10, 8))
+ rng := rand.New(rand.NewSource(time.Now().Unix()))
+
+ for i := 0; i < b.N; i++ {
+ n := big.NewInt(rng.Int63n(maxValues))
+ err := o.GotoLocation(n)
+ assert.Nil(b, err)
+ }
+}
+
+func BenchmarkOdometer_Increment(b *testing.B) {
+ o := New(charset.Numbers, 8, WithRolloverEnabled(true))
+
+ for i := 0; i < b.N; i++ {
+ _ = o.Increment()
+ }
+}
+
+func BenchmarkOdometer_Increment_Big(b *testing.B) {
+ o := New(charset.AllChars, 256)
+
+ for i := 0; i < b.N; i++ {
+ _ = o.Increment()
+ }
+}
+
+func BenchmarkOdometer_IncrementN(b *testing.B) {
+ o := New(charset.Numbers, 8, WithRolloverEnabled(true))
+
+ n := big.NewInt(5)
+ for i := 0; i < b.N; i++ {
+ _ = o.IncrementN(n)
+ }
+}
+
+func BenchmarkOdometer_String(b *testing.B) {
+ o := New(charset.Numbers, 12)
+
+ for i := 0; i < b.N; i++ {
+ _ = o.String()
+ }
+}
diff --git a/odometer/odometer_test.go b/odometer/odometer_test.go
new file mode 100644
index 0000000..00e6e0f
--- /dev/null
+++ b/odometer/odometer_test.go
@@ -0,0 +1,307 @@
+package odometer
+
+import (
+ "errors"
+ "math/big"
+ "testing"
+
+ "github.com/jedib0t/go-passwords/charset"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestOdometer(t *testing.T) {
+ t.Run("default", func(t *testing.T) {
+ o := New(charset.Numbers, 2)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ ok := o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "2", o.Location().String())
+ assert.Equal(t, "01", o.String())
+ assert.Equal(t, []int{0, 1}, o.Value())
+ ok = o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "3", o.Location().String())
+ assert.Equal(t, "02", o.String())
+ assert.Equal(t, []int{0, 2}, o.Value())
+
+ o.Last()
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+ ok = o.Increment()
+ assert.False(t, ok)
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+
+ ok = o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, "99", o.Location().String())
+ assert.Equal(t, "98", o.String())
+ assert.Equal(t, []int{9, 8}, o.Value())
+ ok = o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, "98", o.Location().String())
+ assert.Equal(t, "97", o.String())
+ assert.Equal(t, []int{9, 7}, o.Value())
+
+ o.First()
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+ ok = o.Decrement()
+ assert.False(t, ok)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+ })
+
+ t.Run("rollover", func(t *testing.T) {
+ o := New(charset.Numbers, 2, WithRolloverEnabled(true))
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ ok := o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "2", o.Location().String())
+ assert.Equal(t, "01", o.String())
+ assert.Equal(t, []int{0, 1}, o.Value())
+ ok = o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "3", o.Location().String())
+ assert.Equal(t, "02", o.String())
+ assert.Equal(t, []int{0, 2}, o.Value())
+
+ o.Last()
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+ ok = o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+ ok = o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "2", o.Location().String())
+ assert.Equal(t, "01", o.String())
+ assert.Equal(t, []int{0, 1}, o.Value())
+
+ ok = o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+ ok = o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+ ok = o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, "99", o.Location().String())
+ assert.Equal(t, "98", o.String())
+ assert.Equal(t, []int{9, 8}, o.Value())
+ })
+
+ t.Run("really big odometer", func(t *testing.T) {
+ o := New(charset.AllChars, 256)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", o.String())
+
+ o.Last()
+ assert.Equal(t, "****************************************************************************************************************************************************************************************************************************************************************", o.String())
+ assert.Equal(t, "22135954000460481554501886154749459371625170502600730699163663905247049740079899968480034338379403807827944552623126075988673634259405600148560278663819464589512058373791164736632467335096807212642462431896323483136010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", o.Location().String())
+ ok := o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, "***************************************************************************************************************************************************************************************************************************************************************&", o.String())
+ assert.Equal(t, "22135954000460481554501886154749459371625170502600730699163663905247049740079899968480034338379403807827944552623126075988673634259405600148560278663819464589512058373791164736632467335096807212642462431896323483136009999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", o.Location().String())
+ ok = o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, "***************************************************************************************************************************************************************************************************************************************************************^", o.String())
+ assert.Equal(t, "22135954000460481554501886154749459371625170502600730699163663905247049740079899968480034338379403807827944552623126075988673634259405600148560278663819464589512058373791164736632467335096807212642462431896323483136009999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999998", o.Location().String())
+
+ o.First()
+ assert.Equal(t, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", o.String())
+ assert.Equal(t, "1", o.Location().String())
+ ok = o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB", o.String())
+ assert.Equal(t, "2", o.Location().String())
+ ok = o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC", o.String())
+ assert.Equal(t, "3", o.Location().String())
+ })
+}
+
+func TestOdometer_Decrement(t *testing.T) {
+ o := New(charset.Numbers, 2)
+ assert.Equal(t, []int{0, 0}, o.Value())
+ assert.Equal(t, "1", o.Location().String())
+
+ err := o.GotoLocation(big.NewInt(100))
+ assert.Nil(t, err)
+
+ for idx := int64(99); idx >= 1; idx-- {
+ ok := o.Decrement()
+ assert.True(t, ok)
+ assert.Equal(t, big.NewInt(idx), o.Location(), idx)
+ }
+}
+
+func TestOdometer_DecrementN(t *testing.T) {
+ t.Run("default", func(t *testing.T) {
+ o := New(charset.Numbers, 2)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ ok := o.DecrementN(big.NewInt(5))
+ assert.False(t, ok)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ o.Last()
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+
+ ok = o.DecrementN(big.NewInt(5))
+ assert.True(t, ok)
+ assert.Equal(t, "95", o.Location().String())
+ assert.Equal(t, "94", o.String())
+ assert.Equal(t, []int{9, 4}, o.Value())
+
+ ok = o.DecrementN(big.NewInt(500))
+ assert.False(t, ok)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+ })
+
+ t.Run("rollover", func(t *testing.T) {
+ o := New(charset.Numbers, 2, WithRolloverEnabled(true))
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ ok := o.DecrementN(big.NewInt(1))
+ assert.True(t, ok)
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+
+ ok = o.DecrementN(big.NewInt(5))
+ assert.True(t, ok)
+ assert.Equal(t, "95", o.Location().String())
+ assert.Equal(t, "94", o.String())
+ assert.Equal(t, []int{9, 4}, o.Value())
+
+ ok = o.DecrementN(big.NewInt(500))
+ assert.True(t, ok)
+ assert.Equal(t, "95", o.Location().String())
+ assert.Equal(t, "94", o.String())
+ assert.Equal(t, []int{9, 4}, o.Value())
+ })
+}
+
+func TestOdometer_GotoLocation(t *testing.T) {
+ o := New(charset.Numbers, 2)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ err := o.GotoLocation(big.NewInt(0))
+ assert.NotNil(t, err)
+ assert.True(t, errors.Is(err, ErrInvalidLocation))
+
+ err = o.GotoLocation(big.NewInt(5))
+ assert.Nil(t, err)
+ assert.Equal(t, "5", o.Location().String())
+ assert.Equal(t, "04", o.String())
+ assert.Equal(t, []int{0, 4}, o.Value())
+
+ err = o.GotoLocation(big.NewInt(50))
+ assert.Nil(t, err)
+ assert.Equal(t, "50", o.Location().String())
+ assert.Equal(t, "49", o.String())
+ assert.Equal(t, []int{4, 9}, o.Value())
+
+ err = o.GotoLocation(big.NewInt(100))
+ assert.Nil(t, err)
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+
+ err = o.GotoLocation(big.NewInt(101))
+ assert.NotNil(t, err)
+ assert.True(t, errors.Is(err, ErrInvalidLocation))
+}
+
+func TestOdometer_Increment(t *testing.T) {
+ o := New(charset.Numbers, 2)
+ assert.Equal(t, []int{0, 0}, o.Value())
+ assert.Equal(t, "1", o.Location().String())
+
+ for idx := int64(2); idx <= 100; idx++ {
+ ok := o.Increment()
+ assert.True(t, ok)
+ assert.Equal(t, big.NewInt(idx), o.Location(), idx)
+ }
+}
+
+func TestOdometer_IncrementN(t *testing.T) {
+ t.Run("default", func(t *testing.T) {
+ o := New(charset.Numbers, 2)
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ ok := o.IncrementN(big.NewInt(5))
+ assert.True(t, ok)
+ assert.Equal(t, "6", o.Location().String())
+ assert.Equal(t, "05", o.String())
+ assert.Equal(t, []int{0, 5}, o.Value())
+
+ o.Last()
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+
+ ok = o.IncrementN(big.NewInt(5))
+ assert.False(t, ok)
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+ })
+
+ t.Run("rollover", func(t *testing.T) {
+ o := New(charset.Numbers, 2, WithRolloverEnabled(true))
+ assert.Equal(t, "1", o.Location().String())
+ assert.Equal(t, "00", o.String())
+ assert.Equal(t, []int{0, 0}, o.Value())
+
+ ok := o.IncrementN(big.NewInt(5))
+ assert.True(t, ok)
+ assert.Equal(t, "6", o.Location().String())
+ assert.Equal(t, "05", o.String())
+ assert.Equal(t, []int{0, 5}, o.Value())
+
+ o.Last()
+ assert.Equal(t, "100", o.Location().String())
+ assert.Equal(t, "99", o.String())
+ assert.Equal(t, []int{9, 9}, o.Value())
+
+ ok = o.IncrementN(big.NewInt(5))
+ assert.True(t, ok)
+ assert.Equal(t, "5", o.Location().String())
+ assert.Equal(t, "04", o.String())
+ assert.Equal(t, []int{0, 4}, o.Value())
+ })
+}
diff --git a/odometer/options.go b/odometer/options.go
new file mode 100644
index 0000000..98ac6ab
--- /dev/null
+++ b/odometer/options.go
@@ -0,0 +1,15 @@
+package odometer
+
+type Option func(o *odometer)
+
+var (
+ defaultOptions = []Option{
+ WithRolloverEnabled(false),
+ }
+)
+
+func WithRolloverEnabled(r bool) Option {
+ return func(o *odometer) {
+ o.rollover = r
+ }
+}
diff --git a/odometer/utils.go b/odometer/utils.go
new file mode 100644
index 0000000..46ea98d
--- /dev/null
+++ b/odometer/utils.go
@@ -0,0 +1,14 @@
+package odometer
+
+import (
+ "math/big"
+)
+
+func numValues(base int, length int) *big.Int {
+ if length == 0 {
+ return big.NewInt(0)
+ }
+
+ i, e := big.NewInt(int64(base)), big.NewInt(int64(length))
+ return i.Exp(i, e, nil)
+}
diff --git a/odometer/utils_test.go b/odometer/utils_test.go
new file mode 100644
index 0000000..eca110d
--- /dev/null
+++ b/odometer/utils_test.go
@@ -0,0 +1,15 @@
+package odometer
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_numValues(t *testing.T) {
+ assert.Equal(t, "0", numValues(10, 0).String())
+ assert.Equal(t, "10", numValues(10, 1).String())
+ assert.Equal(t, "10000", numValues(10, 4).String())
+ assert.Equal(t, "100000000", numValues(10, 8).String())
+ assert.Equal(t, "10000000000", numValues(10, 10).String())
+}
diff --git a/password/generator.go b/password/generator.go
index b7e4dec..fc96400 100644
--- a/password/generator.go
+++ b/password/generator.go
@@ -113,8 +113,6 @@ func (g *generator) numSymbolsToGenerate() int {
return 0
}
-func (g *generator) ruleEnforcer() {}
-
func (g *generator) sanitize() (Generator, error) {
// validate the inputs
if len(g.charset) == 0 {
diff --git a/password/sequencer/errors.go b/password/sequencer/errors.go
deleted file mode 100644
index 5164658..0000000
--- a/password/sequencer/errors.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package sequencer
-
-import "errors"
-
-var (
- ErrEmptyCharset = errors.New("cannot generate passwords with empty charset")
- ErrInvalidN = errors.New("value of N exceeds valid range")
- ErrZeroLenPassword = errors.New("cannot generate passwords with 0 length")
-)
diff --git a/password/sequencer/rules.go b/password/sequencer/rules.go
deleted file mode 100644
index 82070b3..0000000
--- a/password/sequencer/rules.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package sequencer
-
-import "github.com/jedib0t/go-passwords/charset"
-
-// Rule controls how the Generator/Sequencer generates passwords.
-type Rule func(s *sequencer)
-
-var (
- basicRules = []Rule{
- WithCharset(charset.AlphaNumeric),
- WithLength(12),
- }
-)
-
-// WithCharset sets the Charset the Generator/Sequencer can use.
-func WithCharset(c charset.Charset) Rule {
- return func(s *sequencer) {
- s.charset = []rune(c)
- }
-}
-
-// WithLength sets the length of the generated password.
-func WithLength(l int) Rule {
- return func(s *sequencer) {
- s.numChars = l
- }
-}
diff --git a/password/sequencer/sequencer.go b/password/sequencer/sequencer.go
deleted file mode 100644
index e9ece08..0000000
--- a/password/sequencer/sequencer.go
+++ /dev/null
@@ -1,313 +0,0 @@
-package sequencer
-
-import (
- "context"
- "fmt"
- "math/big"
- "math/rand/v2"
- "sync"
-
- "github.com/jedib0t/go-passwords/charset"
-)
-
-var (
- biZero = big.NewInt(0)
- biOne = big.NewInt(1)
-)
-
-// Sequencer is a deterministic Password Generator that generates all possible
-// combinations of passwords for a Charset and defined number of characters in
-// the password. It lets you move back and forth through the list of possible
-// passwords, and involves no RNG.
-type Sequencer interface {
- // First moves to the first possible password and returns the same.
- First() string
- // Get returns the current password in the sequence.
- Get() string
- // GetN returns the value for N (location in list of possible passwords).
- GetN() *big.Int
- // GotoN overrides N.
- GotoN(n *big.Int) (string, error)
- // HasNext returns true if there is at least one more password.
- HasNext() bool
- // Last moves to the last possible password and returns the same.
- Last() string
- // Next moves to the next possible password and returns the same.
- Next() string
- // NextN is like Next looped N times, in an optimal way.
- NextN(n *big.Int) string
- // Prev moves to the previous possible password and returns the same.
- Prev() string
- // PrevN is like Prev looped N times, in an optimal way.
- PrevN(n *big.Int) string
- // Reset cleans up state and moves to the first possible word.
- Reset()
- // Stream sends all possible passwords in order to the given channel. If you
- // want to limit output, pass in a *big.Int with the number of passwords you
- // want to be generated and streamed.
- Stream(ctx context.Context, ch chan string, optionalCount ...*big.Int) error
-}
-
-type sequencer struct {
- base *big.Int
- charset []rune
- charsetLen int
- charsetMaxIdx int
- maxWords *big.Int
- mutex sync.Mutex
- n *big.Int
- nMax *big.Int
- numChars int
- password []int
- passwordChars []rune
- passwordMaxIdx int
- rng *rand.Rand
-}
-
-// New returns a password Sequencer.
-func New(rules ...Rule) (Sequencer, error) {
- s := &sequencer{}
- for _, rule := range append(basicRules, rules...) {
- rule(s)
- }
-
- // init the variables
- s.base = big.NewInt(int64(len(s.charset)))
- s.charsetLen = len(s.charset)
- s.charsetMaxIdx = len(s.charset) - 1
- s.maxWords = MaximumPossibleWords(charset.Charset(s.charset), s.numChars)
- s.n = big.NewInt(0)
- s.nMax = new(big.Int).Sub(s.maxWords, biOne)
- s.password = make([]int, s.numChars)
- s.passwordChars = make([]rune, s.numChars)
- s.passwordMaxIdx = s.numChars - 1
-
- if len(s.charset) == 0 {
- return nil, ErrEmptyCharset
- }
- if s.numChars <= 0 {
- return nil, ErrZeroLenPassword
- }
- return s, nil
-}
-
-// First moves to the first possible password and returns the same.
-func (s *sequencer) First() string {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- s.n.Set(biZero)
- for idx := range s.password {
- s.password[idx] = 0
- }
- return s.get()
-}
-
-// Get returns the current password in the sequence.
-func (s *sequencer) Get() string {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- return s.get()
-}
-
-// GetN returns the current location in the list of possible passwords.
-func (s *sequencer) GetN() *big.Int {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- return new(big.Int).Set(s.n)
-}
-
-// GotoN overrides the current location in the list of possible passwords.
-func (s *sequencer) GotoN(n *big.Int) (string, error) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- // ensure n is within possible range (0 to nMax)
- if n.Sign() < 0 || n.Cmp(s.nMax) > 0 {
- return "", fmt.Errorf("%w: n=%s, range=[0 to %s]", ErrInvalidN, n, s.nMax)
- }
-
- // override and compute the word
- s.n.Set(n)
- s.computeWord()
- return s.get(), nil
-}
-
-// HasNext returns true if there is at least one more password.
-func (s *sequencer) HasNext() bool {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- return s.n.Cmp(s.nMax) < 0
-}
-
-// Last moves to the last possible password and returns the same.
-func (s *sequencer) Last() string {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- s.n.Set(s.nMax)
- for idx := range s.password {
- s.password[idx] = s.charsetMaxIdx
- }
- return s.get()
-}
-
-// Next moves to the next possible password and returns the same.
-func (s *sequencer) Next() string {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- s.next()
- return s.get()
-}
-
-// NextN is like Next looped N times, in an optimal way.
-func (s *sequencer) NextN(n *big.Int) string {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- if s.n.Cmp(s.nMax) < 0 {
- s.n.Add(s.n, n)
- s.computeWord()
- }
- return s.get()
-}
-
-// Prev moves to the previous possible password and returns the same.
-func (s *sequencer) Prev() string {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- s.prev()
- return s.get()
-}
-
-// PrevN is like Prev looped N times, in an optimal way.
-func (s *sequencer) PrevN(n *big.Int) string {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- if s.n.Cmp(biZero) > 0 {
- s.n.Sub(s.n, n)
- s.computeWord()
- }
- return s.get()
-}
-
-// Reset cleans up state and moves to the first possible word.
-func (s *sequencer) Reset() {
- s.First()
-}
-
-// Stream sends all possible passwords in order to the given channel. If you
-// want to limit output, pass in a *big.Int with the number of passwords you
-// want to be generated and streamed.
-func (s *sequencer) Stream(ctx context.Context, ch chan string, optionalCount ...*big.Int) error {
- defer close(ch)
-
- maxToBeSent := new(big.Int).Set(s.maxWords)
- if len(optionalCount) == 1 && optionalCount[0] != nil && optionalCount[0].Cmp(biZero) > 0 {
- maxToBeSent.Set(optionalCount[0])
- }
-
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- ch <- s.get()
- chSent := big.NewInt(1)
- for ; s.next() && chSent.Cmp(maxToBeSent) < 0; chSent.Add(chSent, biOne) {
- select {
- case <-ctx.Done():
- return ctx.Err()
- default:
- ch <- s.get()
- }
- }
- return nil
-}
-
-func (s *sequencer) computeWord() {
- // base conversion: convert the value of n to a decimal with a base of
- // wg.charsetLen using continuous division and use all the remainders as the
- // index value to pick the character from the charset
-
- // prep the dividend, remainder and modulus
- dividend, remainder := new(big.Int).Set(s.n), new(big.Int)
- // append values to the password in reverse (from right to left)
- charIdx := s.passwordMaxIdx
- // append every remainder until dividend becomes zero
- for ; dividend.Cmp(biZero) != 0; charIdx-- {
- dividend, remainder = dividend.QuoRem(dividend, s.base, remainder)
- s.password[charIdx] = int(remainder.Int64())
- }
- // left-pad the remaining characters with 0 (==> 0th char in charset)
- for ; charIdx >= 0; charIdx-- {
- s.password[charIdx] = 0
- }
-}
-
-func (s *sequencer) get() string {
- for idx := range s.passwordChars {
- s.passwordChars[idx] = s.charset[s.password[idx]]
- }
- return string(s.passwordChars)
-}
-
-func (s *sequencer) next() bool {
- if s.n.Cmp(s.nMax) >= 0 {
- return false
- }
-
- s.n.Add(s.n, biOne)
- for idx := s.passwordMaxIdx; idx >= 0; idx-- {
- if s.nextAtIndex(idx) {
- return true
- }
- }
- return true
-}
-
-func (s *sequencer) nextAtIndex(idx int) bool {
- if s.password[idx] < s.charsetMaxIdx {
- s.password[idx]++
- return true
- }
- if s.password[idx] == s.charsetMaxIdx && idx > 0 {
- s.password[idx] = 0
- s.nextAtIndex(idx - 1)
- return true
- }
- return false
-}
-
-func (s *sequencer) prev() bool {
- if s.n.Cmp(biZero) <= 0 {
- return false
- }
-
- s.n.Sub(s.n, biOne)
- for idx := s.passwordMaxIdx; idx >= 0; idx-- {
- if s.prevAtIndex(idx) {
- return true
- }
- }
- return true
-}
-
-func (s *sequencer) prevAtIndex(idx int) bool {
- if s.password[idx] > 0 {
- s.password[idx]--
- return true
- }
- if s.password[idx] == 0 && idx > 0 {
- s.password[idx] = s.charsetMaxIdx
- s.prevAtIndex(idx - 1)
- return true
- }
- return false
-}
-
-func (s *sequencer) ruleEnforcer() {}
diff --git a/password/sequencer/sequencer_test.go b/password/sequencer/sequencer_test.go
deleted file mode 100644
index c3b42e7..0000000
--- a/password/sequencer/sequencer_test.go
+++ /dev/null
@@ -1,315 +0,0 @@
-package sequencer
-
-import (
- "context"
- "errors"
- "fmt"
- "math"
- "math/big"
- "testing"
- "time"
-
- "github.com/jedib0t/go-passwords/charset"
- "github.com/stretchr/testify/assert"
-)
-
-func BenchmarkSequencer_GotoN(b *testing.B) {
- s, err := New(
- WithCharset(charset.AlphaNumeric.WithoutAmbiguity().WithoutDuplicates()),
- WithLength(12),
- )
- assert.Nil(b, err)
-
- n := big.NewInt(math.MaxInt)
- pw, err := s.GotoN(n)
- assert.NotEmpty(b, pw)
- assert.Nil(b, err)
- assert.Equal(b, "AXZvFwUyHzQM", pw)
-
- for idx := 0; idx < b.N; idx++ {
- _, _ = s.GotoN(n)
- }
-}
-
-func BenchmarkSequencer_Next(b *testing.B) {
- s, err := New(
- WithCharset(charset.AlphaNumeric.WithoutAmbiguity().WithoutDuplicates()),
- WithLength(12),
- )
- assert.Nil(b, err)
- s.First()
-
- assert.NotEmpty(b, s.Next())
- for idx := 0; idx < b.N; idx++ {
- s.Next()
- }
-}
-
-func BenchmarkSequencer_NextN(b *testing.B) {
- s, err := New(
- WithCharset(charset.AlphaNumeric.WithoutAmbiguity().WithoutDuplicates()),
- WithLength(12),
- )
- assert.Nil(b, err)
- s.First()
-
- n := big.NewInt(100)
- assert.NotEmpty(b, s.NextN(n))
- for idx := 0; idx < b.N; idx++ {
- s.NextN(n)
- }
-}
-
-func BenchmarkSequencer_Prev(b *testing.B) {
- s, err := New(
- WithCharset(charset.AlphaNumeric.WithoutAmbiguity().WithoutDuplicates()),
- WithLength(12),
- )
- assert.Nil(b, err)
- s.Last()
-
- assert.NotEmpty(b, s.Prev())
- for idx := 0; idx < b.N; idx++ {
- s.Prev()
- }
-}
-
-func BenchmarkSequencer_PrevN(b *testing.B) {
- s, err := New(
- WithCharset(charset.AlphaNumeric.WithoutAmbiguity().WithoutDuplicates()),
- WithLength(12),
- )
- assert.Nil(b, err)
- _, _ = s.GotoN(big.NewInt(math.MaxInt))
-
- n := big.NewInt(100)
- assert.NotEmpty(b, s.PrevN(n))
- for idx := 0; idx < b.N; idx++ {
- s.PrevN(n)
- }
-}
-
-func TestSequencer(t *testing.T) {
- s, err := New(
- WithCharset(""),
- WithLength(3),
- )
- assert.Nil(t, s)
- assert.NotNil(t, err)
- assert.True(t, errors.Is(err, ErrEmptyCharset))
- s, err = New(
- WithCharset("AB"),
- WithLength(0),
- )
- assert.Nil(t, s)
- assert.NotNil(t, err)
- assert.True(t, errors.Is(err, ErrZeroLenPassword))
-
- s, err = New(
- WithCharset("AB"),
- WithLength(3),
- )
- assert.Nil(t, err)
- assert.Equal(t, "AAA", s.Get())
- assert.Equal(t, "0", s.GetN().String())
- assert.Equal(t, "AAA", s.First())
- assert.Equal(t, "0", s.GetN().String())
- assert.True(t, s.HasNext())
- assert.Equal(t, "BAA", s.NextN(big.NewInt(4)))
- assert.Equal(t, "4", s.GetN().String())
- assert.True(t, s.HasNext())
- assert.Equal(t, "BBA", s.NextN(big.NewInt(2)))
- assert.Equal(t, "6", s.GetN().String())
- assert.True(t, s.HasNext())
- assert.Equal(t, "BAA", s.PrevN(big.NewInt(2)))
- assert.Equal(t, "4", s.GetN().String())
- assert.True(t, s.HasNext())
- assert.Equal(t, "AAA", s.PrevN(big.NewInt(4)))
- assert.Equal(t, "0", s.GetN().String())
- assert.True(t, s.HasNext())
- assert.Equal(t, "BBB", s.Last())
- assert.Equal(t, "7", s.GetN().String())
- assert.False(t, s.HasNext())
- assert.Equal(t, "BBB", s.Get())
-
- // Next()
- expectedAnswers := []string{
- "AAA",
- "AAB",
- "ABA",
- "ABB",
- "BAA",
- "BAB",
- "BBA",
- "BBB",
- }
- s.Reset()
- assert.Equal(t, expectedAnswers[0], s.Get())
- for idx := 1; idx < len(expectedAnswers); idx++ {
- assert.Equal(t, expectedAnswers[idx], s.Next())
- }
- assert.Equal(t, "BBB", s.Next())
- assert.Equal(t, "BBB", s.NextN(big.NewInt(303)))
-
- // Prev()
- expectedAnswers = []string{
- "BBB",
- "BBA",
- "BAB",
- "BAA",
- "ABB",
- "ABA",
- "AAB",
- "AAA",
- }
- s.Last()
- assert.Equal(t, expectedAnswers[0], s.Get())
- for idx := 1; idx < len(expectedAnswers); idx++ {
- assert.Equal(t, expectedAnswers[idx], s.Prev())
- }
- assert.Equal(t, "AAA", s.Prev())
- assert.Equal(t, "AAA", s.PrevN(big.NewInt(303)))
-}
-
-func TestSequencer_GotoN(t *testing.T) {
- s, err := New(
- WithCharset("AB"),
- WithLength(3),
- )
- assert.Nil(t, err)
-
- pw, err := s.GotoN(big.NewInt(-1))
- assert.NotNil(t, err)
- assert.True(t, errors.Is(err, ErrInvalidN))
- pw, err = s.GotoN(big.NewInt(100))
- assert.NotNil(t, err)
- assert.True(t, errors.Is(err, ErrInvalidN))
-
- s, err = New(
- WithCharset("AB"),
- WithLength(4),
- )
- assert.Nil(t, err)
- expectedPasswords := []string{
- "AAAA",
- "AAAB",
- "AABA",
- "AABB",
- "ABAA",
- "ABAB",
- "ABBA",
- "ABBB",
- "BAAA",
- "BAAB",
- "BABA",
- "BABB",
- "BBAA",
- "BBAB",
- "BBBA",
- "BBBB",
- }
- for idx := 0; idx < len(expectedPasswords); idx++ {
- pw, err = s.GotoN(big.NewInt(int64(idx)))
- assert.Nil(t, err)
- assert.Equal(t, expectedPasswords[idx], pw, fmt.Sprintf("idx=%d", idx))
- }
-}
-
-func TestSequencer_Stream(t *testing.T) {
- s, err := New(
- WithCharset("AB"),
- WithLength(3),
- )
- assert.Nil(t, err)
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
-
- ch := make(chan string, 1)
- // streams all possible passwords into the channel in an async routine
- go func() {
- //t.Logf("< streaming passwords ...")
- err2 := s.Stream(ctx, ch)
- assert.Nil(t, err2)
- //t.Logf("< streaming passwords ... done!")
- }()
-
- // listen on channel for passwords until channel is closed, or until timeout
- pw, ok := "", true
- var passwords []string
- //t.Logf("> receiving passwords ...")
- for ok {
- select {
- case <-ctx.Done():
- assert.Fail(t, ctx.Err().Error())
- ok = false
- case pw, ok = <-ch:
- if ok {
- //t.Logf("> ++ received %#v", pw)
- passwords = append(passwords, pw)
- }
- }
- }
- //t.Logf("> receiving passwords ... done!")
-
- // verify received passwords
- expectedPasswords := []string{
- "AAA",
- "AAB",
- "ABA",
- "ABB",
- "BAA",
- "BAB",
- "BBA",
- "BBB",
- }
- assert.Equal(t, expectedPasswords, passwords)
-}
-
-func TestSequencer_Stream_Limited(t *testing.T) {
- s, err := New(
- WithCharset("AB"),
- WithLength(3),
- )
- assert.Nil(t, err)
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
-
- ch := make(chan string, 1)
- // streams all possible passwords into the channel in an async routine
- go func() {
- //t.Logf("< streaming passwords ...")
- err2 := s.Stream(ctx, ch, big.NewInt(5))
- assert.Nil(t, err2)
- //t.Logf("< streaming passwords ... done!")
- }()
-
- // listen on channel for passwords until channel is closed, or until timeout
- pw, ok := "", true
- var passwords []string
- //t.Logf("> receiving passwords ...")
- for ok {
- select {
- case <-ctx.Done():
- assert.Fail(t, ctx.Err().Error())
- ok = false
- case pw, ok = <-ch:
- if ok {
- //t.Logf("> ++ received %#v", pw)
- passwords = append(passwords, pw)
- }
- }
- }
- //t.Logf("> receiving passwords ... done!")
-
- // verify received passwords
- expectedPasswords := []string{
- "AAA",
- "AAB",
- "ABA",
- "ABB",
- "BAA",
- }
- assert.Equal(t, expectedPasswords, passwords)
-}
diff --git a/password/sequencer/utils.go b/password/sequencer/utils.go
deleted file mode 100644
index 87d9fc1..0000000
--- a/password/sequencer/utils.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package sequencer
-
-import (
- "math/big"
-
- "github.com/jedib0t/go-passwords/charset"
-)
-
-// MaximumPossibleWords returns the maximum number of unique passwords that can
-// be generated with the given Charset and the number of characters allowed in
-// the password.
-func MaximumPossibleWords(charset charset.Charset, numChars int) *big.Int {
- i, e := big.NewInt(int64(len(charset))), big.NewInt(int64(numChars))
- return i.Exp(i, e, nil)
-}
diff --git a/password/sequencer/utils_test.go b/password/sequencer/utils_test.go
deleted file mode 100644
index 894765a..0000000
--- a/password/sequencer/utils_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package sequencer
-
-import (
- "testing"
-
- "github.com/jedib0t/go-passwords/charset"
- "github.com/stretchr/testify/assert"
-)
-
-func TestMaximumPossibleWords(t *testing.T) {
- assert.Equal(t, "10", MaximumPossibleWords(charset.Numbers, 1).String())
- assert.Equal(t, "10000", MaximumPossibleWords(charset.Numbers, 4).String())
- assert.Equal(t, "100000000", MaximumPossibleWords(charset.Numbers, 8).String())
- assert.Equal(t, "16777216", MaximumPossibleWords(charset.Symbols, 8).String())
- assert.Equal(t, "377801998336", MaximumPossibleWords(charset.SymbolsFull, 8).String())
- assert.Equal(t, "53459728531456", MaximumPossibleWords(charset.Alphabets, 8).String())
- assert.Equal(t, "218340105584896", MaximumPossibleWords(charset.AlphaNumeric, 8).String())
- assert.Equal(t, "576480100000000", MaximumPossibleWords(charset.AllChars, 8).String())
- assert.Equal(t, "4304672100000000", MaximumPossibleWords(charset.AlphaNumeric+charset.SymbolsFull, 8).String())
-}