mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
351a01ff0c
An interval renewal turned the client's reset days into milliseconds with no bound, so a value that wraps int64 (a node reports it unchecked) sent the catch-up loop stepping through time for seconds per client while the traffic writer held the database. Intervals above 36500 days no longer renew, and every client write refuses them.
97 lines
3.7 KiB
Go
97 lines
3.7 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
)
|
|
|
|
func TestWeeklyRenewalSearchFailsClosed(t *testing.T) {
|
|
// Keep a background clock reader so -race catches global timezone writes.
|
|
started, stop, done := make(chan struct{}), make(chan struct{}), make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
_ = time.Now()
|
|
close(started)
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
default:
|
|
_ = time.Now()
|
|
}
|
|
}
|
|
}()
|
|
<-started
|
|
t.Cleanup(func() { close(stop); <-done })
|
|
|
|
// Fault injection: a valid TZif with twelve absent Sundays, not a claim
|
|
// about real IANA zones. Each following Monday repeats, so no Sunday returns.
|
|
const transitions = 24
|
|
data := make([]byte, 44+transitions*5+2*6+2)
|
|
copy(data, "TZif")
|
|
binary.BigEndian.PutUint32(data[32:36], transitions)
|
|
binary.BigEndian.PutUint32(data[36:40], 2)
|
|
binary.BigEndian.PutUint32(data[40:44], 2)
|
|
from := time.Date(2026, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
for week := range 12 {
|
|
at := from.AddDate(0, 0, (week+1)*7).Unix()
|
|
for day := range 2 {
|
|
index := week*2 + day
|
|
binary.BigEndian.PutUint32(data[44+index*4:48+index*4], uint32(at+int64(day)*86400))
|
|
data[44+transitions*4+index] = byte(1 - day)
|
|
}
|
|
}
|
|
binary.BigEndian.PutUint32(data[44+transitions*5+6:48+transitions*5+6], 86400)
|
|
data[len(data)-2] = 'X'
|
|
loc, err := time.LoadLocationFromTZData("Test/SkippedSundays", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := nextWeeklyRenewal(from, 7, loc); !got.Equal(from) {
|
|
t.Fatalf("exhausted weekly search advanced to %s, want unchanged %s", got.Format(time.RFC3339), from.Format(time.RFC3339))
|
|
}
|
|
traffic := &xray.ClientTraffic{ExpiryTime: from.UnixMilli(), ResetWeekday: 7, ResetMax: 4, ResetCount: 2, Enable: false, Up: 111, Down: 222}
|
|
before := *traffic
|
|
expiry, renewals := catchUpClientRenewal(traffic, from.AddDate(0, 0, 28).UnixMilli(), loc)
|
|
if expiry != before.ExpiryTime || renewals != 0 || *traffic != before {
|
|
t.Fatalf("exhausted search changed billing/traffic: expiry=%d renewals=%d traffic=%+v", expiry, renewals, traffic)
|
|
}
|
|
for _, tt := range []struct {
|
|
name string
|
|
now int64
|
|
}{
|
|
{"initial suggestion exhausted", from.UnixMilli()},
|
|
{"catch-up exhausted with allowances left", from.AddDate(0, 3, 0).UnixMilli()},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
preview, err := previewClientRenewal(ClientRenewalPreviewRequest{
|
|
ExpiryTime: before.ExpiryTime, ResetWeekday: 7, ResetMax: 4, ResetCount: 2,
|
|
}, tt.now, loc)
|
|
if err == nil || err.Error() != "calendar renewal could not find a future expiry\n" || preview != nil {
|
|
t.Fatalf("failed search preview/error = %+v/%v, want nil/calendar search error", preview, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A node reports the renewal interval unchecked; one that wraps int64 when turned into
|
|
// milliseconds made the catch-up loop step through time for seconds per client.
|
|
func TestCatchUpClientRenewalRefusesAnOversizedInterval(t *testing.T) {
|
|
now := time.Now().UnixMilli()
|
|
for _, reset := range []int{106751991167, maxClientResetDays + 1} {
|
|
traffic := &xray.ClientTraffic{Email: "c", Reset: reset, ExpiryTime: now - 86400000}
|
|
start := time.Now()
|
|
expiry, renewals := catchUpClientRenewal(traffic, now, time.UTC)
|
|
if expiry != traffic.ExpiryTime || renewals != 0 || time.Since(start) > 100*time.Millisecond {
|
|
t.Fatalf("reset %d: expiry %d, renewals %d after %v; want no renewal", reset, expiry, renewals, time.Since(start))
|
|
}
|
|
}
|
|
traffic := &xray.ClientTraffic{Email: "c", Reset: maxClientResetDays, ExpiryTime: now - 86400000}
|
|
if _, renewals := catchUpClientRenewal(traffic, now, time.UTC); renewals != 1 {
|
|
t.Fatalf("a %d-day interval renewed %d times, want once", maxClientResetDays, renewals)
|
|
}
|
|
}
|