github.com/braveheart12/insolar-09-08-19@v0.8.7/network/controller/bootstrap/session_manager.go (about) 1 /* 2 * The Clear BSD License 3 * 4 * Copyright (c) 2019 Insolar Technologies 5 * 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: 9 * 10 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 * Neither the name of Insolar Technologies nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 * 14 * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 * 16 */ 17 18 package bootstrap 19 20 import ( 21 "context" 22 "fmt" 23 "sort" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/insolar/insolar/component" 29 "github.com/insolar/insolar/core" 30 "github.com/insolar/insolar/instrumentation/inslogger" 31 "github.com/insolar/insolar/network/utils" 32 "github.com/pkg/errors" 33 ) 34 35 type SessionID uint64 36 37 //go:generate stringer -type=SessionState 38 type SessionState uint8 39 40 const ( 41 Authorized SessionState = iota + 1 42 Challenge1 43 Challenge2 44 ) 45 46 const ( 47 stateRunning = uint32(iota + 1) 48 stateIdle 49 ) 50 51 type Session struct { 52 NodeID core.RecordRef 53 Cert core.AuthorizationCertificate 54 State SessionState 55 56 DiscoveryNonce Nonce 57 58 Time time.Time 59 TTL time.Duration 60 } 61 62 func (s *Session) expirationTime() time.Time { 63 return s.Time.Add(s.TTL) 64 } 65 66 type sessionWithID struct { 67 *Session 68 SessionID 69 } 70 71 type notification struct{} 72 73 type SessionManager interface { 74 component.Starter 75 component.Stopper 76 77 NewSession(ref core.RecordRef, cert core.AuthorizationCertificate, ttl time.Duration) SessionID 78 CheckSession(id SessionID, expected SessionState) error 79 SetDiscoveryNonce(id SessionID, discoveryNonce Nonce) error 80 GetChallengeData(id SessionID) (core.AuthorizationCertificate, Nonce, error) 81 ChallengePassed(id SessionID) error 82 ReleaseSession(id SessionID) (*Session, error) 83 ProlongateSession(id SessionID, session *Session) 84 } 85 86 type sessionManager struct { 87 sequence uint64 88 lock sync.RWMutex 89 sessions map[SessionID]*Session 90 state uint32 91 92 newSessionNotification chan notification 93 stopCleanupNotification chan notification 94 } 95 96 func NewSessionManager() SessionManager { 97 return &sessionManager{ 98 sessions: make(map[SessionID]*Session), 99 newSessionNotification: make(chan notification), 100 stopCleanupNotification: make(chan notification), 101 state: stateIdle, 102 } 103 } 104 105 func (sm *sessionManager) Start(ctx context.Context) error { 106 logger := inslogger.FromContext(ctx) 107 logger.Debug("[ sessionManager::Start ] start cleaning up sessions") 108 109 if atomic.CompareAndSwapUint32(&sm.state, stateIdle, stateRunning) { 110 go sm.cleanupExpiredSessions() 111 } else { 112 logger.Warn("[ sessionManager::Start ] Called twice") 113 } 114 115 return nil 116 } 117 118 func (sm *sessionManager) Stop(ctx context.Context) error { 119 logger := inslogger.FromContext(ctx) 120 logger.Debug("[ sessionManager::Stop ] stop cleaning up sessions") 121 122 if atomic.CompareAndSwapUint32(&sm.state, stateRunning, stateIdle) { 123 sm.stopCleanupNotification <- notification{} 124 } else { 125 logger.Warn("[ sessionManager::Stop ] Called twice") 126 } 127 128 return nil 129 } 130 131 func (sm *sessionManager) NewSession(ref core.RecordRef, cert core.AuthorizationCertificate, ttl time.Duration) SessionID { 132 id := utils.AtomicLoadAndIncrementUint64(&sm.sequence) 133 session := &Session{ 134 NodeID: ref, 135 State: Authorized, 136 Cert: cert, 137 Time: time.Now(), 138 TTL: ttl, 139 } 140 sessionID := SessionID(id) 141 sm.addSession(sessionID, session) 142 return sessionID 143 } 144 145 func (sm *sessionManager) addSession(id SessionID, session *Session) { 146 sm.lock.Lock() 147 sm.sessions[id] = session 148 sm.lock.Unlock() 149 150 sm.newSessionNotification <- notification{} 151 } 152 153 func (sm *sessionManager) CheckSession(id SessionID, expected SessionState) error { 154 sm.lock.RLock() 155 defer sm.lock.RUnlock() 156 157 _, err := sm.checkSession(id, expected) 158 return err 159 } 160 161 func (sm *sessionManager) checkSession(id SessionID, expected SessionState) (*Session, error) { 162 session := sm.sessions[id] 163 if session == nil { 164 return nil, errors.New(fmt.Sprintf("no such session ID: %d", id)) 165 } 166 if session.State != expected { 167 return nil, errors.New(fmt.Sprintf("session %d should have state %s but has %s", id, expected, session.State)) 168 } 169 return session, nil 170 } 171 172 func (sm *sessionManager) SetDiscoveryNonce(id SessionID, discoveryNonce Nonce) error { 173 sm.lock.Lock() 174 defer sm.lock.Unlock() 175 176 session, err := sm.checkSession(id, Authorized) 177 if err != nil { 178 return err 179 } 180 session.DiscoveryNonce = discoveryNonce 181 session.State = Challenge1 182 return nil 183 } 184 185 func (sm *sessionManager) GetChallengeData(id SessionID) (core.AuthorizationCertificate, Nonce, error) { 186 sm.lock.Lock() 187 defer sm.lock.Unlock() 188 189 session, err := sm.checkSession(id, Challenge1) 190 if err != nil { 191 return nil, nil, err 192 } 193 return session.Cert, session.DiscoveryNonce, nil 194 } 195 196 func (sm *sessionManager) ChallengePassed(id SessionID) error { 197 sm.lock.Lock() 198 defer sm.lock.Unlock() 199 200 session, err := sm.checkSession(id, Challenge1) 201 if err != nil { 202 return err 203 } 204 session.State = Challenge2 205 return nil 206 } 207 208 func (sm *sessionManager) ReleaseSession(id SessionID) (*Session, error) { 209 sm.lock.Lock() 210 defer sm.lock.Unlock() 211 212 session, err := sm.checkSession(id, Challenge2) 213 if err != nil { 214 return nil, err 215 } 216 delete(sm.sessions, id) 217 return session, nil 218 } 219 220 func (sm *sessionManager) ProlongateSession(id SessionID, session *Session) { 221 session.Time = time.Now() 222 sm.addSession(id, session) 223 } 224 225 func (sm *sessionManager) cleanupExpiredSessions() { 226 var sessionsByExpirationTime []*sessionWithID 227 for { 228 sm.lock.RLock() 229 sessionsCount := len(sm.sessions) 230 sm.lock.RUnlock() 231 232 // We missed notification 233 if sessionsCount != 0 && len(sessionsByExpirationTime) == 0 { 234 sessionsByExpirationTime = sm.sortSessionsByExpirationTime() 235 } 236 237 // Session count is zero - wait for first session added. 238 if len(sessionsByExpirationTime) == 0 { 239 // Have no active sessions. 240 // Block until sessions will be added or session manager begins to stop. 241 select { 242 case <-sm.newSessionNotification: 243 sessionsByExpirationTime = sm.sortSessionsByExpirationTime() 244 case <-sm.stopCleanupNotification: 245 return 246 } 247 248 // Check session instantly released concurrently 249 if len(sessionsByExpirationTime) == 0 { 250 continue 251 } 252 } 253 254 // Get expiration time for next session and wait for it 255 nextSessionToExpire := sessionsByExpirationTime[0] 256 waitTime := time.Until(nextSessionToExpire.expirationTime()) 257 258 select { 259 case <-sm.newSessionNotification: 260 // Handle new session. reorder expiration short list 261 sessionsByExpirationTime = sm.sortSessionsByExpirationTime() 262 263 case <-time.After(waitTime): 264 // Move forward through sessions and check whether we should delete the session 265 sessionsByExpirationTime = sm.expireSessions(sessionsByExpirationTime) 266 267 case <-sm.stopCleanupNotification: 268 return 269 } 270 } 271 } 272 273 func (sm *sessionManager) sortSessionsByExpirationTime() []*sessionWithID { 274 sm.lock.RLock() 275 276 // Read active session with their ids. We have to store them as a slice to keep ordering by expiration time. 277 sessionsByExpirationTime := make([]*sessionWithID, 0, len(sm.sessions)) 278 for sessionID, session := range sm.sessions { 279 sessionsByExpirationTime = append(sessionsByExpirationTime, &sessionWithID{ 280 SessionID: sessionID, 281 Session: session, 282 }) 283 } 284 285 sm.lock.RUnlock() 286 287 sort.SliceStable(sessionsByExpirationTime, func(i, j int) bool { 288 expirationTime1 := sessionsByExpirationTime[i].expirationTime() 289 expirationTime2 := sessionsByExpirationTime[j].expirationTime() 290 291 return expirationTime1.Before(expirationTime2) 292 }) 293 294 return sessionsByExpirationTime 295 } 296 297 func (sm *sessionManager) expireSessions(sessionsByExpirationTime []*sessionWithID) []*sessionWithID { 298 var shift int 299 300 sm.lock.Lock() 301 302 for i, session := range sessionsByExpirationTime { 303 // Check when we have to stop expire 304 if session.expirationTime().After(time.Now()) { 305 break 306 } 307 308 delete(sm.sessions, session.SessionID) 309 shift = i + 1 310 } 311 312 sm.lock.Unlock() 313 314 return sessionsByExpirationTime[shift:] 315 }