diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index a8912e8e..cd7f7659 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -232,7 +232,7 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] { return nil } - if !c.staleReturn && c.maxAge > 0 && le.Value.expires <= time.Now().Unix() { + if !c.staleReturn && le.Value.expires > 0 && le.Value.expires <= time.Now().Unix() { c.deleteElement(le) c.maybeDeleteOldest() diff --git a/common/domain/matcher.go b/common/domain/matcher.go index 258f3f91..83cbd485 100644 --- a/common/domain/matcher.go +++ b/common/domain/matcher.go @@ -14,14 +14,19 @@ type Matcher struct { } func NewMatcher(domains []string, domainSuffix []string) *Matcher { - domainList := make([]string, 0, len(domains)+len(domainSuffix)) + domainList := make([]string, 0, len(domains)+2*len(domainSuffix)) seen := make(map[string]bool, len(domainList)) for _, domain := range domainSuffix { if seen[domain] { continue } seen[domain] = true - domainList = append(domainList, reverseDomainSuffix(domain)) + if domain[0] == '.' { + domainList = append(domainList, reverseDomainSuffix(domain)) + } else { + domainList = append(domainList, reverseDomain(domain)) + domainList = append(domainList, reverseRootDomainSuffix(domain)) + } } for _, domain := range domains { if seen[domain] { @@ -134,3 +139,16 @@ func reverseDomainSuffix(domain string) string { b[l] = prefixLabel return string(b) } + +func reverseRootDomainSuffix(domain string) string { + l := len(domain) + b := make([]byte, l+2) + for i := 0; i < l; { + r, n := utf8.DecodeRuneInString(domain[i:]) + i += n + utf8.EncodeRune(b[l-i:], r) + } + b[l] = '.' + b[l+1] = prefixLabel + return string(b) +} diff --git a/common/json/badjson/array.go b/common/json/badjson/array.go index f662f183..a7d5f70f 100644 --- a/common/json/badjson/array.go +++ b/common/json/badjson/array.go @@ -3,12 +3,25 @@ package badjson import ( "bytes" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" ) type JSONArray []any +func (a JSONArray) IsEmpty() bool { + if len(a) == 0 { + return true + } + return common.All(a, func(it any) bool { + if valueInterface, valueMaybeEmpty := it.(isEmpty); valueMaybeEmpty && valueInterface.IsEmpty() { + return true + } + return false + }) +} + func (a JSONArray) MarshalJSON() ([]byte, error) { return json.Marshal([]any(a)) } diff --git a/common/json/badjson/empty.go b/common/json/badjson/empty.go new file mode 100644 index 00000000..da8bedf5 --- /dev/null +++ b/common/json/badjson/empty.go @@ -0,0 +1,5 @@ +package badjson + +type isEmpty interface { + IsEmpty() bool +} diff --git a/common/json/badjson/object.go b/common/json/badjson/object.go index 225d3afd..61d5862d 100644 --- a/common/json/badjson/object.go +++ b/common/json/badjson/object.go @@ -15,11 +15,23 @@ type JSONObject struct { linkedhashmap.Map[string, any] } -func (m JSONObject) MarshalJSON() ([]byte, error) { +func (m *JSONObject) IsEmpty() bool { + if m.Size() == 0 { + return true + } + return common.All(m.Entries(), func(it collections.MapEntry[string, any]) bool { + if valueInterface, valueMaybeEmpty := it.Value.(isEmpty); valueMaybeEmpty && valueInterface.IsEmpty() { + return true + } + return false + }) +} + +func (m *JSONObject) MarshalJSON() ([]byte, error) { buffer := new(bytes.Buffer) buffer.WriteString("{") items := common.Filter(m.Entries(), func(it collections.MapEntry[string, any]) bool { - if valueObject, valueIsJSONObject := it.Value.(*JSONObject); valueIsJSONObject && valueObject.IsEmpty() { + if valueInterface, valueMaybeEmpty := it.Value.(isEmpty); valueMaybeEmpty && valueInterface.IsEmpty() { return false } return true diff --git a/common/winpowrprof/event.go b/common/winpowrprof/event.go new file mode 100644 index 00000000..addc1c53 --- /dev/null +++ b/common/winpowrprof/event.go @@ -0,0 +1,12 @@ +package winpowrprof + +const ( + EVENT_SUSPEND = iota + EVENT_RESUME + EVENT_RESUME_AUTOMATIC // Because the user is not present, most applications should do nothing. +) + +type EventListener interface { + Start() error + Close() error +} diff --git a/common/winpowrprof/event_stub.go b/common/winpowrprof/event_stub.go new file mode 100644 index 00000000..c9598ec0 --- /dev/null +++ b/common/winpowrprof/event_stub.go @@ -0,0 +1,11 @@ +//go:build !windows + +package winpowrprof + +import ( + "os" +) + +func NewEventListener(callback func(event int)) (EventListener, error) { + return nil, os.ErrInvalid +} diff --git a/common/winpowrprof/event_test.go b/common/winpowrprof/event_test.go new file mode 100644 index 00000000..1113fe3c --- /dev/null +++ b/common/winpowrprof/event_test.go @@ -0,0 +1,19 @@ +package winpowrprof + +import ( + "runtime" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPowerEvents(t *testing.T) { + if runtime.GOOS != "windows" { + t.SkipNow() + } + listener, err := NewEventListener(func(event int) {}) + require.NoError(t, err) + require.NotNil(t, listener) + require.NoError(t, listener.Start()) + require.NoError(t, listener.Close()) +} diff --git a/common/winpowrprof/event_windows.go b/common/winpowrprof/event_windows.go new file mode 100644 index 00000000..15895dc2 --- /dev/null +++ b/common/winpowrprof/event_windows.go @@ -0,0 +1,83 @@ +package winpowrprof + +// modify from https://github.com/golang/go/blob/b634f6fdcbebee23b7da709a243f3db217b64776/src/runtime/os_windows.go#L257 + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modpowerprof = windows.NewLazySystemDLL("powrprof.dll") + procPowerRegisterSuspendResumeNotification = modpowerprof.NewProc("PowerRegisterSuspendResumeNotification") + procPowerUnregisterSuspendResumeNotification = modpowerprof.NewProc("PowerUnregisterSuspendResumeNotification") +) + +const ( + PBT_APMSUSPEND uint32 = 4 + PBT_APMRESUMESUSPEND uint32 = 7 + PBT_APMRESUMEAUTOMATIC uint32 = 18 +) + +const ( + _DEVICE_NOTIFY_CALLBACK = 2 +) + +type _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct { + callback uintptr + context uintptr +} + +type eventListener struct { + params _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS + handle uintptr +} + +func NewEventListener(callback func(event int)) (EventListener, error) { + if err := procPowerRegisterSuspendResumeNotification.Find(); err != nil { + return nil, err // Running on Windows 7, where we don't need it anyway. + } + if err := procPowerUnregisterSuspendResumeNotification.Find(); err != nil { + return nil, err // Running on Windows 7, where we don't need it anyway. + } + + var fn interface{} = func(context uintptr, changeType uint32, setting uintptr) uintptr { + switch changeType { + case PBT_APMSUSPEND: + callback(EVENT_SUSPEND) + case PBT_APMRESUMESUSPEND: + callback(EVENT_RESUME) + case PBT_APMRESUMEAUTOMATIC: + callback(EVENT_RESUME_AUTOMATIC) + } + return 0 + } + return &eventListener{ + params: _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS{ + callback: windows.NewCallback(fn), + }, + }, nil +} + +func (l *eventListener) Start() error { + _, _, errno := syscall.SyscallN( + procPowerRegisterSuspendResumeNotification.Addr(), + _DEVICE_NOTIFY_CALLBACK, + uintptr(unsafe.Pointer(&l.params)), + uintptr(unsafe.Pointer(&l.handle)), + ) + if errno != 0 { + return errno + } + return nil +} + +func (l *eventListener) Close() error { + _, _, errno := syscall.SyscallN(procPowerUnregisterSuspendResumeNotification.Addr(), uintptr(unsafe.Pointer(&l.handle))) + if errno != 0 { + return errno + } + return nil +}