Skip to content

Commit 013e7c0

Browse files
committed
session: introduce Reserve->Create pattern
In this commit, we let StateReserved be the new initial state of a session for when NewSession is called. We then do predicate checks for linked sessions along with unique session alias (ID) and priv key derivations all under the same DB transaction in NewSession. ShiftState then moves a session to StateCreated. Only in StateCreated does a session become usable. With this change, we no longer need to ensure atomic session creation by acquiring the `sessRegMu` mutex in the session RPC server.
1 parent 88a2bf0 commit 013e7c0

File tree

4 files changed

+205
-227
lines changed

4 files changed

+205
-227
lines changed

session/interface.go

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ const (
2727
type State uint8
2828

2929
/*
30-
/---> StateExpired (terminal)
31-
StateCreated ---
32-
\---> StateRevoked (terminal)
30+
/---> StateExpired (terminal)
31+
StateReserved ---> StateCreated ---
32+
\---> StateRevoked (terminal)
3333
*/
3434

3535
const (
3636
// StateCreated is the state of a session once it has been fully
37-
// committed to the Store and is ready to be used. This is the first
38-
// state of a session.
37+
// committed to the BoltStore and is ready to be used. This is the
38+
// first state after StateReserved.
3939
StateCreated State = 0
4040

4141
// StateInUse is the state of a session that is currently being used.
@@ -52,10 +52,10 @@ const (
5252
// date.
5353
StateExpired State = 3
5454

55-
// StateReserved is a temporary initial state of a session. On start-up,
56-
// any sessions in this state should be cleaned up.
57-
//
58-
// NOTE: this isn't used yet.
55+
// StateReserved is a temporary initial state of a session. This is used
56+
// to reserve a unique ID and private key pair for a session before it
57+
// is fully created. On start-up, any sessions in this state should be
58+
// cleaned up.
5959
StateReserved State = 4
6060
)
6161

@@ -67,6 +67,9 @@ func (s State) Terminal() bool {
6767
// legalStateShifts is a map that defines the legal State transitions that a
6868
// Session can be put through.
6969
var legalStateShifts = map[State]map[State]bool{
70+
StateReserved: {
71+
StateCreated: true,
72+
},
7073
StateCreated: {
7174
StateExpired: true,
7275
StateRevoked: true,
@@ -141,7 +144,7 @@ func buildSession(id ID, localPrivKey *btcec.PrivateKey, label string, typ Type,
141144
sess := &Session{
142145
ID: id,
143146
Label: label,
144-
State: StateCreated,
147+
State: StateReserved,
145148
Type: typ,
146149
Expiry: expiry.UTC(),
147150
CreatedAt: created.UTC(),
@@ -185,23 +188,13 @@ type IDToGroupIndex interface {
185188
// retrieving Terminal Connect sessions.
186189
type Store interface {
187190
// NewSession creates a new session with the given user-defined
188-
// parameters.
189-
//
190-
// NOTE: currently this purely a constructor of the Session type and
191-
// does not make any database calls. This will be changed in a future
192-
// commit.
193-
NewSession(id ID, localPrivKey *btcec.PrivateKey, label string,
194-
typ Type, expiry time.Time, serverAddr string, devServer bool,
195-
perms []bakery.Op, caveats []macaroon.Caveat,
191+
// parameters. The session will remain in the StateReserved state until
192+
// ShiftState is called to update the state.
193+
NewSession(label string, typ Type, expiry time.Time, serverAddr string,
194+
devServer bool, perms []bakery.Op, caveats []macaroon.Caveat,
196195
featureConfig FeaturesConfig, privacy bool, linkedGroupID *ID,
197196
flags PrivacyFlags) (*Session, error)
198197

199-
// CreateSession adds a new session to the store. If a session with the
200-
// same local public key already exists an error is returned. This
201-
// can only be called with a Session with an ID that the Store has
202-
// reserved.
203-
CreateSession(*Session) error
204-
205198
// GetSession fetches the session with the given key.
206199
GetSession(key *btcec.PublicKey) (*Session, error)
207200

@@ -220,12 +213,6 @@ type Store interface {
220213
UpdateSessionRemotePubKey(localPubKey,
221214
remotePubKey *btcec.PublicKey) error
222215

223-
// GetUnusedIDAndKeyPair can be used to generate a new, unused, local
224-
// private key and session ID pair. Care must be taken to ensure that no
225-
// other thread calls this before the returned ID and key pair from this
226-
// method are either used or discarded.
227-
GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error)
228-
229216
// GetSessionByID fetches the session with the given ID.
230217
GetSessionByID(id ID) (*Session, error)
231218

session/kvdb_store.go

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -182,41 +182,42 @@ func getSessionKey(session *Session) []byte {
182182
return session.LocalPublicKey.SerializeCompressed()
183183
}
184184

185-
// NewSession creates a new session with the given user-defined parameters.
186-
//
187-
// NOTE: currently this purely a constructor of the Session type and does not
188-
// make any database calls. This will be changed in a future commit.
185+
// NewSession creates and persists a new session with the given user-defined
186+
// parameters. The initial state of the session will be Reserved until
187+
// ShiftState is called with StateCreated.
189188
//
190189
// NOTE: this is part of the Store interface.
191-
func (db *BoltStore) NewSession(id ID, localPrivKey *btcec.PrivateKey,
192-
label string, typ Type, expiry time.Time, serverAddr string,
193-
devServer bool, perms []bakery.Op, caveats []macaroon.Caveat,
194-
featureConfig FeaturesConfig, privacy bool, linkedGroupID *ID,
195-
flags PrivacyFlags) (*Session, error) {
196-
197-
return buildSession(
198-
id, localPrivKey, label, typ, db.clock.Now(), expiry,
199-
serverAddr, devServer, perms, caveats, featureConfig, privacy,
200-
linkedGroupID, flags,
201-
)
202-
}
190+
func (db *BoltStore) NewSession(label string, typ Type, expiry time.Time,
191+
serverAddr string, devServer bool, perms []bakery.Op,
192+
caveats []macaroon.Caveat, featureConfig FeaturesConfig, privacy bool,
193+
linkedGroupID *ID, flags PrivacyFlags) (*Session, error) {
203194

204-
// CreateSession adds a new session to the store. If a session with the same
205-
// local public key already exists an error is returned.
206-
//
207-
// NOTE: this is part of the Store interface.
208-
func (db *BoltStore) CreateSession(session *Session) error {
209-
sessionKey := getSessionKey(session)
210-
211-
return db.Update(func(tx *bbolt.Tx) error {
195+
var session *Session
196+
err := db.Update(func(tx *bbolt.Tx) error {
212197
sessionBucket, err := getBucket(tx, sessionBucketKey)
213198
if err != nil {
214199
return err
215200
}
216201

202+
id, localPrivKey, err := getUnusedIDAndKeyPair(sessionBucket)
203+
if err != nil {
204+
return err
205+
}
206+
207+
session, err = buildSession(
208+
id, localPrivKey, label, typ, db.clock.Now(), expiry,
209+
serverAddr, devServer, perms, caveats, featureConfig,
210+
privacy, linkedGroupID, flags,
211+
)
212+
if err != nil {
213+
return err
214+
}
215+
216+
sessionKey := getSessionKey(session)
217+
217218
if len(sessionBucket.Get(sessionKey)) != 0 {
218-
return fmt.Errorf("session with local public "+
219-
"key(%x) already exists",
219+
return fmt.Errorf("session with local public key(%x) "+
220+
"already exists",
220221
session.LocalPublicKey.SerializeCompressed())
221222
}
222223

@@ -275,6 +276,11 @@ func (db *BoltStore) CreateSession(session *Session) error {
275276

276277
return putSession(sessionBucket, session)
277278
})
279+
if err != nil {
280+
return nil, err
281+
}
282+
283+
return session, nil
278284
}
279285

280286
// UpdateSessionRemotePubKey can be used to add the given remote pub key
@@ -577,53 +583,35 @@ func (db *BoltStore) GetSessionByID(id ID) (*Session, error) {
577583
return session, nil
578584
}
579585

580-
// GetUnusedIDAndKeyPair can be used to generate a new, unused, local private
586+
// getUnusedIDAndKeyPair can be used to generate a new, unused, local private
581587
// key and session ID pair. Care must be taken to ensure that no other thread
582588
// calls this before the returned ID and key pair from this method are either
583589
// used or discarded.
584-
//
585-
// NOTE: this is part of the Store interface.
586-
func (db *BoltStore) GetUnusedIDAndKeyPair() (ID, *btcec.PrivateKey, error) {
587-
var (
588-
id ID
589-
privKey *btcec.PrivateKey
590-
)
591-
err := db.Update(func(tx *bbolt.Tx) error {
592-
sessionBucket, err := getBucket(tx, sessionBucketKey)
593-
if err != nil {
594-
return err
595-
}
596-
597-
idIndexBkt := sessionBucket.Bucket(idIndexKey)
598-
if idIndexBkt == nil {
599-
return ErrDBInitErr
600-
}
590+
func getUnusedIDAndKeyPair(bucket *bbolt.Bucket) (ID, *btcec.PrivateKey,
591+
error) {
601592

602-
// Spin until we find a key with an ID that does not collide
603-
// with any of our existing IDs.
604-
for {
605-
// Generate a new private key and ID pair.
606-
privKey, id, err = NewSessionPrivKeyAndID()
607-
if err != nil {
608-
return err
609-
}
593+
idIndexBkt := bucket.Bucket(idIndexKey)
594+
if idIndexBkt == nil {
595+
return ID{}, nil, ErrDBInitErr
596+
}
610597

611-
// Check that no such ID exits in our id-to-key index.
612-
idBkt := idIndexBkt.Bucket(id[:])
613-
if idBkt != nil {
614-
continue
615-
}
598+
// Spin until we find a key with an ID that does not collide with any of
599+
// our existing IDs.
600+
for {
601+
// Generate a new private key and ID pair.
602+
privKey, id, err := NewSessionPrivKeyAndID()
603+
if err != nil {
604+
return ID{}, nil, err
605+
}
616606

617-
break
607+
// Check that no such ID exits in our id-to-key index.
608+
idBkt := idIndexBkt.Bucket(id[:])
609+
if idBkt != nil {
610+
continue
618611
}
619612

620-
return nil
621-
})
622-
if err != nil {
623-
return id, nil, err
613+
return id, privKey, nil
624614
}
625-
626-
return id, privKey, nil
627615
}
628616

629617
// GetGroupID will return the group ID for the given session ID.

0 commit comments

Comments
 (0)