Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inwx: improve sleep calculation #2086

Merged
merged 1 commit into from
Jan 19, 2024
Merged

Conversation

ldez
Copy link
Member

@ldez ldez commented Jan 18, 2024

#2084 (comment)

package foo

import (
	"encoding/base32"
	"testing"
	"time"

	"github.com/pquerna/otp/totp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestTOTP(t *testing.T) {
	testCases := []struct {
		desc     string
		now      string
		expected string
		counter  string
	}{
		{
			desc:     "TAN A",
			now:      "2024-01-01T06:29:59Z",
			expected: "801386",
			counter:  "2024-01-01T06:29:30Z",
		},
		{
			desc:     "TAN B [1]",
			now:      "2024-01-01T06:30:00Z",
			expected: "078251",
			counter:  "2024-01-01T06:30:00Z",
		},
		{
			desc:     "TAN B [2]",
			now:      "2024-01-01T06:30:29Z",
			expected: "078251",
			counter:  "2024-01-01T06:30:00Z",
		},
		{
			desc:     "TAN C [1]",
			now:      "2024-01-01T06:30:30Z",
			expected: "521951",
			counter:  "2024-01-01T06:30:30Z",
		},
		{
			desc:     "TAN C [2]",
			now:      "2024-01-01T06:30:31Z",
			expected: "521951",
			counter:  "2024-01-01T06:30:30Z",
		},
		{
			desc:     "TAN D [1]",
			now:      "2024-01-01T06:31:10Z",
			expected: "650525",
			counter:  "2024-01-01T06:31:00Z",
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			now, err := time.Parse(time.RFC3339, test.now)
			require.NoError(t, err)

			tan, err := totp.GenerateCode(base32.StdEncoding.EncodeToString([]byte("secret")), now)
			require.NoError(t, err)

			assert.Equal(t, test.expected, tan)

			counter, err := time.Parse(time.RFC3339, test.counter)
			require.NoError(t, err)

			assert.Equal(t, counter, now.Truncate(30*time.Second))
		})
	}
}

@ldez
Copy link
Member Author

ldez commented Jan 18, 2024

@gnoack can you take a look?

if err != nil {
return err
}

d.previousUnlock = time.Now()
d.previousUnlock = now.Truncate(30 * time.Second)
Copy link
Contributor

@gnoack gnoack Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time.Truncate() [1] is rounding down to multiples of the duration since the Go zero time [2], but that is a different baseline time than the Unix epoch on 1970-01-01. (I assume there are probably various leap seconds or other time drift corrections in between, so that these two points in time are not aligned on the same 30-second period?)

[1] https://pkg.go.dev/time#Time.Truncate
[2] https://pkg.go.dev/time#Time.IsZero

Apart from that, I think this approach works well -- if you replace this Truncate call with one which truncates to multiples of 30 seconds since the Unix epoch, (or if you double check that I am wrong about the leap seconds theory), that should work. (maybe it's worth calculating a valid boundary by hand for the test and exercising it there, to be sure)

Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure to see what can be difference as we are using second 🤔

https://go.dev/play/p/SECZB_iROSw

Maybe I miss something, can you give me more explanation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@gnoack gnoack Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, and your solution is more elegant indeed. Let's use Time.Truncate() :)

package trunctime                                                                      
                                                                                       
import (                                                                               
        "testing"                                                                      
        "time"                                                                         
)                                                                                      
                                                                                       
func FuzzTruncation(f *testing.F) {                                                    
        for _, uts := range []int64{                                                   
                0, 1234567890, 99,                                                     
        } {                                                                            
                f.Add(uts)                                                             
        }                                                                              
        f.Fuzz(func(t *testing.T, uts int64) {                                         
                if uts <= 0 {                                                          
                        t.Skip("Negative timestamp")                                   
                }                                                                      
                ts := time.Unix(uts, 0)                                                
                                                                                       
                tstrunc := ts.Truncate(30 * time.Second)                               
                utstrunc := time.Unix(30*(uts/30), 0)                                  
                                                                                       
                if !tstrunc.Equal(utstrunc) {                                          
                        t.Errorf("tstrunc %v != utstrunc %v", tstrunc, utstrunc)       
                }                                                                      
        })                                                                             
}

I threw a fuzzer at it, to be sure, and it did not find any differences after a few tens-of-million attempts :)

...
fuzz: elapsed: 3m36s, execs: 49078748 (227838/sec), new interesting: 2 (total: 5)
fuzz: elapsed: 3m39s, execs: 49767357 (229712/sec), new interesting: 2 (total: 5)
fuzz: elapsed: 3m42s, execs: 50445798 (226109/sec), new interesting: 2 (total: 5)
^Cfuzz: elapsed: 3m43s, execs: 50697296 (229938/sec), new interesting: 2 (total: 5)
PASS
ok  	github.com/gnoack/go-experiment/trunctime	223.097s

Copy link
Contributor

@gnoack gnoack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this looks good!

@ldez ldez marked this pull request as ready for review January 19, 2024 11:55
@ldez ldez requested a review from dmke January 19, 2024 11:55
@ldez ldez merged commit 7fd1704 into go-acme:master Jan 19, 2024
7 checks passed
@ldez ldez deleted the feat/inwx-improve-sleep branch January 19, 2024 12:55
@ldez ldez added this to the v4.15 milestone Jan 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants