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  }