package session import ( "context" "errors" "fmt" "strings" "sync" ) // MemoryCache stores session record snapshots in process-local memory. It is // intended for the authenticated gateway hot path and deliberately keeps no // TTL or size-based eviction policy. type MemoryCache struct { mu sync.RWMutex records map[string]Record } // NewMemoryCache constructs an empty process-local session snapshot store. func NewMemoryCache() *MemoryCache { return &MemoryCache{ records: make(map[string]Record), } } // Lookup resolves deviceSessionID from the process-local snapshot map. func (c *MemoryCache) Lookup(ctx context.Context, deviceSessionID string) (Record, error) { if c == nil { return Record{}, errors.New("lookup session from in-memory cache: nil cache") } if ctx == nil || fmt.Sprint(ctx) == "context.TODO" { return Record{}, errors.New("lookup session from in-memory cache: nil context") } if strings.TrimSpace(deviceSessionID) == "" { return Record{}, errors.New("lookup session from in-memory cache: empty device session id") } c.mu.RLock() record, ok := c.records[deviceSessionID] c.mu.RUnlock() if !ok { return Record{}, fmt.Errorf("lookup session from in-memory cache: %w", ErrNotFound) } return cloneRecord(record), nil } // Upsert stores record in the process-local snapshot map after validating the // same session invariants expected from the Redis-backed cache. func (c *MemoryCache) Upsert(record Record) error { if c == nil { return errors.New("upsert session into in-memory cache: nil cache") } if err := validateRecord(record.DeviceSessionID, record); err != nil { return fmt.Errorf("upsert session into in-memory cache: %w", err) } cloned := cloneRecord(record) c.mu.Lock() c.records[record.DeviceSessionID] = cloned c.mu.Unlock() return nil } // Delete removes the local snapshot for deviceSessionID when one exists. func (c *MemoryCache) Delete(deviceSessionID string) { if c == nil || strings.TrimSpace(deviceSessionID) == "" { return } c.mu.Lock() delete(c.records, deviceSessionID) c.mu.Unlock() } func cloneRecord(record Record) Record { cloned := record if record.RevokedAtMS != nil { value := *record.RevokedAtMS cloned.RevokedAtMS = &value } return cloned } var _ SnapshotStore = (*MemoryCache)(nil)