package transporttoken import ( "crypto/rand" "encoding/hex" "fmt" "strings" "sync" "time" ) type record struct { baseRevision int64 digest string expiresAt time.Time } type Store struct { mu sync.Mutex ttl time.Duration records map[string]record } func NewStore(ttl time.Duration) *Store { if ttl <= 0 { ttl = 10 * time.Minute } return &Store{ ttl: ttl, records: map[string]record{}, } } func (s *Store) Issue(prefix string, baseRevision int64, digest string) string { token := strings.TrimSpace(prefix) + NewTokenHex(10) now := time.Now() s.mu.Lock() defer s.mu.Unlock() s.cleanupLocked(now) s.records[token] = record{ baseRevision: baseRevision, digest: digest, expiresAt: now.Add(s.ttl), } return token } func (s *Store) Consume(token string, baseRevision int64, digest string) bool { token = strings.TrimSpace(token) if token == "" { return false } now := time.Now() s.mu.Lock() defer s.mu.Unlock() s.cleanupLocked(now) rec, ok := s.records[token] if !ok { return false } delete(s.records, token) if rec.baseRevision != baseRevision { return false } if rec.digest != digest { return false } if now.After(rec.expiresAt) { return false } return true } func (s *Store) cleanupLocked(now time.Time) { for k, rec := range s.records { if now.After(rec.expiresAt) { delete(s.records, k) } } } func NewTokenHex(n int) string { if n <= 0 { n = 8 } buf := make([]byte, n) if _, err := rand.Read(buf); err != nil { return fmt.Sprintf("%d", time.Now().UnixNano()) } return hex.EncodeToString(buf) }