package store import ( "sort" "strings" "time" "github.com/google/uuid" ) func (m *Memory) ListPeerDiscoveries(tenantID, status string) ([]*BGPPeerDiscovery, error) { m.mu.RLock() defer m.mu.RUnlock() status = strings.TrimSpace(strings.ToLower(status)) var out []*BGPPeerDiscovery for _, d := range m.peerDiscoveries { if d == nil || d.TenantID != tenantID { continue } if status != "" && !strings.EqualFold(d.Status, status) { continue } out = append(out, copyPeerDiscovery(d)) } sort.Slice(out, func(i, j int) bool { return out[i].LastSeenAt.After(out[j].LastSeenAt) }) return out, nil } func (m *Memory) GetPeerDiscovery(tenantID, id string) (*BGPPeerDiscovery, error) { m.mu.RLock() defer m.mu.RUnlock() d, ok := m.peerDiscoveries[id] if !ok || d.TenantID != tenantID { return nil, ErrNotFound } return copyPeerDiscovery(d), nil } func (m *Memory) UpsertPeerDiscovery(tenantID string, in *PeerDiscoveryUpsert) (*BGPPeerDiscovery, error) { if in == nil { return nil, ErrInvalidInput } neighbor, ok := NormalizePeerNeighborString(in.Neighbor) if !ok { return nil, ErrInvalidInput } neighborID := strings.TrimSpace(in.NeighborID) seenAt := in.SeenAt if seenAt.IsZero() { seenAt = time.Now().UTC() } m.mu.Lock() defer m.mu.Unlock() if _, ok := m.tenants[tenantID]; !ok { return nil, ErrTenantScope } existing := m.findPeerDiscoveryLocked(tenantID, neighborID, neighbor, in.RemoteASN) if existing != nil { if existing.Status == PeerDiscoveryRejected { return copyPeerDiscovery(existing), nil } if existing.Status == PeerDiscoveryApproved { existing.SessionState = strings.TrimSpace(in.SessionState) existing.ProtocolName = strings.TrimSpace(in.ProtocolName) existing.LastSeenAt = seenAt if sp := strings.TrimSpace(in.SpeakerID); sp != "" { existing.SpeakerID = sp } return copyPeerDiscovery(existing), nil } existing.Neighbor = neighbor existing.RemoteASN = in.RemoteASN if neighborID != "" { existing.NeighborID = neighborID } existing.ProtocolName = strings.TrimSpace(in.ProtocolName) existing.SessionState = strings.TrimSpace(in.SessionState) existing.LastSeenAt = seenAt if sp := strings.TrimSpace(in.SpeakerID); sp != "" { existing.SpeakerID = sp } return copyPeerDiscovery(existing), nil } id := uuid.NewString() d := &BGPPeerDiscovery{ ID: id, TenantID: tenantID, SpeakerID: strings.TrimSpace(in.SpeakerID), NeighborID: neighborID, Neighbor: neighbor, RemoteASN: in.RemoteASN, ProtocolName: strings.TrimSpace(in.ProtocolName), SessionState: strings.TrimSpace(in.SessionState), Status: PeerDiscoveryPending, FirstSeenAt: seenAt, LastSeenAt: seenAt, } m.peerDiscoveries[id] = d return copyPeerDiscovery(d), nil } func (m *Memory) ApprovePeerDiscovery(tenantID, id string, in *PeerDiscoveryApproveInput) (*BGPPeer, *BGPPeerDiscovery, error) { m.mu.Lock() defer m.mu.Unlock() d, ok := m.peerDiscoveries[id] if !ok || d.TenantID != tenantID { return nil, nil, ErrNotFound } if d.Status != PeerDiscoveryPending { return nil, nil, ErrInvalidInput } if d.RemoteASN == 0 { return nil, nil, ErrInvalidInput } neighbor, okN := NormalizePeerNeighborString(d.Neighbor) if !okN { return nil, nil, ErrInvalidInput } name := "" enabled := true var speakerID *string if in != nil { name = strings.TrimSpace(in.Name) if in.Enabled != nil { enabled = *in.Enabled } if in.SpeakerID != nil { v := strings.TrimSpace(*in.SpeakerID) if v != "" { speakerID = &v } } } if name == "" { if d.NeighborID != "" { name = "discovered-" + d.NeighborID } else { name = "discovered-" + neighbor } } if speakerID == nil && strings.TrimSpace(d.SpeakerID) != "" { sp := d.SpeakerID speakerID = &sp } peerID := uuid.NewString() peer := &BGPPeer{ ID: peerID, TenantID: tenantID, SpeakerID: speakerID, Name: name, Neighbor: neighbor, RemoteASN: d.RemoteASN, Enabled: enabled, SessionState: d.SessionState, PoliciesJSON: "{}", } m.peers[peerID] = peer d.Status = PeerDiscoveryApproved d.ApprovedPeerID = peerID d.LastSeenAt = time.Now().UTC() peerCopy := *peer return &peerCopy, copyPeerDiscovery(d), nil } func (m *Memory) RejectPeerDiscovery(tenantID, id string) (*BGPPeerDiscovery, error) { m.mu.Lock() defer m.mu.Unlock() d, ok := m.peerDiscoveries[id] if !ok || d.TenantID != tenantID { return nil, ErrNotFound } if d.Status == PeerDiscoveryApproved { return nil, ErrInvalidInput } d.Status = PeerDiscoveryRejected d.LastSeenAt = time.Now().UTC() return copyPeerDiscovery(d), nil } func (m *Memory) findPeerDiscoveryLocked(tenantID, neighborID, neighbor string, asn int64) *BGPPeerDiscovery { for _, d := range m.peerDiscoveries { if d == nil || d.TenantID != tenantID { continue } if neighborID != "" && d.NeighborID == neighborID { return d } if neighborID == "" && d.NeighborID == "" && d.Neighbor == neighbor && d.RemoteASN == asn { return d } if neighborID != "" && d.NeighborID == "" && d.Neighbor == neighbor && d.RemoteASN == asn { return d } } return nil } func copyPeerDiscovery(d *BGPPeerDiscovery) *BGPPeerDiscovery { if d == nil { return nil } cp := *d return &cp }