Files
zeromesh/internal/vl1/peer.go

194 lines
3.7 KiB
Go

package vl1
import (
"log/slog"
"net"
"sync"
"time"
"zeromesh/internal/identity"
)
type Peer struct {
Address identity.Address
PublicKey [32]byte
Endpoint *net.UDPAddr
Cipher *NoiseCipher
LastSeen time.Time
LastSend time.Time
Connected bool
mu sync.RWMutex
log *slog.Logger
}
func (p *Peer) Touch() {
p.mu.Lock()
defer p.mu.Unlock()
p.LastSeen = time.Now()
}
func (p *Peer) SetCipher(c *NoiseCipher) {
p.mu.Lock()
defer p.mu.Unlock()
p.Cipher = c
p.Connected = true
}
func (p *Peer) IsConnected() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.Connected
}
func (p *Peer) IsAlive() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return time.Since(p.LastSeen) < 90*time.Second
}
func (p *Peer) Encrypt(plaintext []byte) ([]byte, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return nil, ErrNoCipher
}
return p.Cipher.Encrypt(plaintext)
}
func (p *Peer) Decrypt(ciphertext []byte) ([]byte, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return nil, ErrNoCipher
}
return p.Cipher.Decrypt(ciphertext)
}
func (p *Peer) EncryptTo(buf []byte, plaintext []byte) (int, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return 0, ErrNoCipher
}
return p.Cipher.EncryptTo(buf, plaintext)
}
func (p *Peer) DecryptTo(buf []byte, ciphertext []byte) ([]byte, error) {
p.mu.RLock()
defer p.mu.RUnlock()
if p.Cipher == nil {
return nil, ErrNoCipher
}
return p.Cipher.DecryptTo(buf, ciphertext)
}
func (p *Peer) NeedsKeepalive() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.Connected && time.Since(p.LastSend) > 25*time.Second
}
var ErrNoCipher = errNoCipher()
func errNoCipher() error {
return &noCipherError{}
}
type noCipherError struct{}
func (e *noCipherError) Error() string {
return "no cipher established"
}
type PeerManager struct {
peers map[identity.Address]*Peer
mu sync.RWMutex
log *slog.Logger
}
func NewPeerManager(log *slog.Logger) *PeerManager {
return &PeerManager{
peers: make(map[identity.Address]*Peer),
log: log,
}
}
func (pm *PeerManager) AddPeer(addr identity.Address, pubKey [32]byte, endpoint *net.UDPAddr) *Peer {
pm.mu.Lock()
defer pm.mu.Unlock()
peer := &Peer{
Address: addr,
PublicKey: pubKey,
Endpoint: endpoint,
LastSeen: time.Now(),
LastSend: time.Now(),
log: pm.log.With("peer", addr.String()),
}
pm.peers[addr] = peer
return peer
}
func (pm *PeerManager) GetPeer(addr identity.Address) *Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
return pm.peers[addr]
}
func (pm *PeerManager) GetPeerByEndpoint(endpoint *net.UDPAddr) *Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
for _, p := range pm.peers {
if p.Endpoint != nil && p.Endpoint.IP.Equal(endpoint.IP) && p.Endpoint.Port == endpoint.Port {
return p
}
}
return nil
}
func (pm *PeerManager) RemovePeer(addr identity.Address) {
pm.mu.Lock()
defer pm.mu.Unlock()
delete(pm.peers, addr)
}
func (pm *PeerManager) UpdatePeerEndpoint(addr identity.Address, endpoint *net.UDPAddr) {
pm.mu.Lock()
defer pm.mu.Unlock()
if p, ok := pm.peers[addr]; ok {
p.Endpoint = endpoint
}
}
func (pm *PeerManager) ConnectedPeers() []*Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
var result []*Peer
for _, p := range pm.peers {
if p.IsConnected() {
result = append(result, p)
}
}
return result
}
func (pm *PeerManager) AllPeers() []*Peer {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make([]*Peer, 0, len(pm.peers))
for _, p := range pm.peers {
result = append(result, p)
}
return result
}
func (pm *PeerManager) CleanDead() {
pm.mu.Lock()
defer pm.mu.Unlock()
for addr, p := range pm.peers {
if p.Connected && !p.IsAlive() {
p.log.Warn("peer timed out, removing")
delete(pm.peers, addr)
}
}
}