@@ -38,19 +38,27 @@ type sessionRecord struct {
3838 VerifyCode [4 ]byte
3939 Signature [sessionInitDataSize ]byte
4040 CreatedAt time.Time
41+ LastActivityAt time.Time
4142 ReuseUntil time.Time
4243}
4344
45+ type closedSessionRecord struct {
46+ Cookie uint8
47+ ExpiresAt time.Time
48+ }
49+
4450type sessionStore struct {
45- mu sync.Mutex
46- nextID uint16
47- byID [maxServerSessions ]* sessionRecord
48- bySig map [[sessionInitDataSize ]byte ]uint8
51+ mu sync.Mutex
52+ nextID uint16
53+ byID [maxServerSessions ]* sessionRecord
54+ bySig map [[sessionInitDataSize ]byte ]uint8
55+ recentClosed map [uint8 ]closedSessionRecord
4956}
5057
5158func newSessionStore () * sessionStore {
5259 return & sessionStore {
53- bySig : make (map [[sessionInitDataSize ]byte ]uint8 , 64 ),
60+ bySig : make (map [[sessionInitDataSize ]byte ]uint8 , 64 ),
61+ recentClosed : make (map [uint8 ]closedSessionRecord , 32 ),
5462 }
5563}
5664
@@ -71,6 +79,7 @@ func (s *sessionStore) findOrCreate(payload []byte, uploadCompressionType uint8,
7179 if sessionID , ok := s .bySig [signature ]; ok {
7280 if existing := s .byID [sessionID ]; existing != nil {
7381 if now .Before (existing .ReuseUntil ) || now .Equal (existing .ReuseUntil ) {
82+ existing .LastActivityAt = now
7483 return existing , true , nil
7584 }
7685 }
@@ -83,11 +92,12 @@ func (s *sessionStore) findOrCreate(payload []byte, uploadCompressionType uint8,
8392 }
8493
8594 record := & sessionRecord {
86- ID : uint8 (slot ),
87- ResponseMode : payload [0 ],
88- CreatedAt : now ,
89- ReuseUntil : now .Add (sessionInitTTL ),
90- Signature : signature ,
95+ ID : uint8 (slot ),
96+ ResponseMode : payload [0 ],
97+ CreatedAt : now ,
98+ LastActivityAt : now ,
99+ ReuseUntil : now .Add (sessionInitTTL ),
100+ Signature : signature ,
91101 }
92102 record .UploadCompression = compression .NormalizeType (uploadCompressionType )
93103 record .DownloadCompression = compression .NormalizeType (downloadCompressionType )
@@ -98,6 +108,7 @@ func (s *sessionStore) findOrCreate(payload []byte, uploadCompressionType uint8,
98108
99109 s .byID [slot ] = record
100110 s .bySig [signature ] = uint8 (slot )
111+ delete (s .recentClosed , uint8 (slot ))
101112 s .nextID = uint16 ((slot + 1 ) % maxServerSessions )
102113 return record , false , nil
103114}
@@ -111,6 +122,109 @@ func (s *sessionStore) expireReuseLocked(now time.Time) {
111122 }
112123}
113124
125+ func (s * sessionStore ) Touch (sessionID uint8 , now time.Time ) bool {
126+ s .mu .Lock ()
127+ defer s .mu .Unlock ()
128+
129+ record := s .byID [sessionID ]
130+ if record == nil {
131+ return false
132+ }
133+ record .LastActivityAt = now
134+ return true
135+ }
136+
137+ func (s * sessionStore ) Active (sessionID uint8 ) (* sessionRecord , bool ) {
138+ s .mu .Lock ()
139+ defer s .mu .Unlock ()
140+
141+ record := s .byID [sessionID ]
142+ if record == nil {
143+ return nil , false
144+ }
145+ copyRecord := * record
146+ return & copyRecord , true
147+ }
148+
149+ func (s * sessionStore ) ExpectedCookie (sessionID uint8 ) (uint8 , bool ) {
150+ s .mu .Lock ()
151+ defer s .mu .Unlock ()
152+
153+ if record := s .byID [sessionID ]; record != nil {
154+ return record .Cookie , true
155+ }
156+ if record , ok := s .recentClosed [sessionID ]; ok {
157+ return record .Cookie , true
158+ }
159+ return 0 , false
160+ }
161+
162+ func (s * sessionStore ) ValidateCookie (sessionID uint8 , cookie uint8 ) bool {
163+ expected , ok := s .ExpectedCookie (sessionID )
164+ return ok && expected == cookie
165+ }
166+
167+ func (s * sessionStore ) Close (sessionID uint8 , now time.Time , retention time.Duration ) bool {
168+ s .mu .Lock ()
169+ defer s .mu .Unlock ()
170+
171+ record := s .byID [sessionID ]
172+ if record == nil {
173+ return false
174+ }
175+
176+ delete (s .bySig , record .Signature )
177+ s .byID [sessionID ] = nil
178+ if retention > 0 {
179+ s .recentClosed [sessionID ] = closedSessionRecord {
180+ Cookie : record .Cookie ,
181+ ExpiresAt : now .Add (retention ),
182+ }
183+ } else {
184+ delete (s .recentClosed , sessionID )
185+ }
186+ return true
187+ }
188+
189+ func (s * sessionStore ) Cleanup (now time.Time , idleTimeout time.Duration , closedRetention time.Duration ) []uint8 {
190+ s .mu .Lock ()
191+ defer s .mu .Unlock ()
192+
193+ s .expireReuseLocked (now )
194+
195+ for sessionID , record := range s .recentClosed {
196+ if ! now .Before (record .ExpiresAt ) {
197+ delete (s .recentClosed , sessionID )
198+ }
199+ }
200+
201+ if idleTimeout <= 0 {
202+ return nil
203+ }
204+
205+ expired := make ([]uint8 , 0 , 8 )
206+ for sessionID , record := range s .byID {
207+ if record == nil {
208+ continue
209+ }
210+ if now .Sub (record .LastActivityAt ) < idleTimeout {
211+ continue
212+ }
213+
214+ delete (s .bySig , record .Signature )
215+ s .byID [sessionID ] = nil
216+ if closedRetention > 0 {
217+ s .recentClosed [uint8 (sessionID )] = closedSessionRecord {
218+ Cookie : record .Cookie ,
219+ ExpiresAt : now .Add (closedRetention ),
220+ }
221+ }
222+ expired = append (expired , uint8 (sessionID ))
223+ }
224+
225+ return expired
226+ }
227+
114228func (s * sessionStore ) allocateSlotLocked () int {
115229 for i := range maxServerSessions {
116230 slot := int ((s .nextID + uint16 (i )) % maxServerSessions )
0 commit comments