137 lines
3.1 KiB
Go
137 lines
3.1 KiB
Go
|
|
package store
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Memory is the Phase-1 in-process Store. Thread-safe via a single RWMutex;
|
||
|
|
// each call is cheap enough that per-table locks would be over-engineering.
|
||
|
|
type Memory struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
sessions map[string]Session
|
||
|
|
previews map[string]Preview
|
||
|
|
approvals map[string]Approval
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewMemory returns an empty Memory store.
|
||
|
|
func NewMemory() *Memory {
|
||
|
|
return &Memory{
|
||
|
|
sessions: map[string]Session{},
|
||
|
|
previews: map[string]Preview{},
|
||
|
|
approvals: map[string]Approval{},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Memory) CreateSession(_ context.Context, s Session) error {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
m.sessions[s.ID] = s
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Memory) GetSession(_ context.Context, id string) (Session, error) {
|
||
|
|
m.mu.RLock()
|
||
|
|
defer m.mu.RUnlock()
|
||
|
|
s, ok := m.sessions[id]
|
||
|
|
if !ok {
|
||
|
|
return Session{}, ErrNotFound
|
||
|
|
}
|
||
|
|
return s, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Memory) SavePreview(_ context.Context, p Preview) error {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
m.previews[p.ID] = p
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Memory) GetPreview(_ context.Context, id string) (Preview, error) {
|
||
|
|
m.mu.RLock()
|
||
|
|
defer m.mu.RUnlock()
|
||
|
|
p, ok := m.previews[id]
|
||
|
|
if !ok {
|
||
|
|
return Preview{}, ErrNotFound
|
||
|
|
}
|
||
|
|
// Return a copy so callers cannot mutate our byte slices.
|
||
|
|
return clonePreview(p), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ConsumePreview marks the preview as consumed if it is still valid and
|
||
|
|
// unconsumed. The returned Preview is the pre-mutation copy.
|
||
|
|
func (m *Memory) ConsumePreview(_ context.Context, id string, now time.Time) (Preview, error) {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
p, ok := m.previews[id]
|
||
|
|
if !ok {
|
||
|
|
return Preview{}, ErrNotFound
|
||
|
|
}
|
||
|
|
if p.ConsumedAt != nil {
|
||
|
|
return Preview{}, ErrAlreadyConsumed
|
||
|
|
}
|
||
|
|
snapshot := clonePreview(p)
|
||
|
|
ts := now
|
||
|
|
p.ConsumedAt = &ts
|
||
|
|
m.previews[id] = p
|
||
|
|
return snapshot, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Memory) SaveApproval(_ context.Context, a Approval) error {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
m.approvals[a.ID] = a
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Memory) MarkApprovalLogged(_ context.Context, id string, logIndex int64, loggedAt time.Time) error {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
a, ok := m.approvals[id]
|
||
|
|
if !ok {
|
||
|
|
return ErrNotFound
|
||
|
|
}
|
||
|
|
a.TLogStatus = TLogLogged
|
||
|
|
a.LogIndex = &logIndex
|
||
|
|
a.LoggedAt = &loggedAt
|
||
|
|
a.TLogError = ""
|
||
|
|
m.approvals[id] = a
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *Memory) MarkApprovalTLogError(_ context.Context, id string, errorMsg string) error {
|
||
|
|
m.mu.Lock()
|
||
|
|
defer m.mu.Unlock()
|
||
|
|
a, ok := m.approvals[id]
|
||
|
|
if !ok {
|
||
|
|
return ErrNotFound
|
||
|
|
}
|
||
|
|
// Keep status at attestation-pending — a reconciler will retry. We
|
||
|
|
// only record the last error message for diagnostics.
|
||
|
|
a.TLogError = errorMsg
|
||
|
|
m.approvals[id] = a
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetApproval is a test-side accessor; not part of the Store interface
|
||
|
|
// because the HTTP handlers never need it.
|
||
|
|
func (m *Memory) GetApproval(id string) (Approval, bool) {
|
||
|
|
m.mu.RLock()
|
||
|
|
defer m.mu.RUnlock()
|
||
|
|
a, ok := m.approvals[id]
|
||
|
|
return a, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
func clonePreview(p Preview) Preview {
|
||
|
|
out := p
|
||
|
|
out.ContentCanonical = append([]byte(nil), p.ContentCanonical...)
|
||
|
|
out.Salt = append([]byte(nil), p.Salt...)
|
||
|
|
out.Nonce = append([]byte(nil), p.Nonce...)
|
||
|
|
if p.ConsumedAt != nil {
|
||
|
|
t := *p.ConsumedAt
|
||
|
|
out.ConsumedAt = &t
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|