Files
meowlib/client/peerstorage.go
T
yves 8b106db52f
continuous-integration/drone/push Build is failing
duplicate messages send fixes
2026-04-21 15:53:56 +02:00

275 lines
6.6 KiB
Go

package client
//
// Storage
//
import (
"crypto/sha256"
"encoding/json"
"errors"
"path/filepath"
"sort"
"sync"
"forge.redroom.link/yves/meowlib"
"github.com/dgraph-io/badger"
"github.com/google/uuid"
)
type PeerStorage struct {
DbFile string `json:"db_file,omitempty"`
mu sync.RWMutex
db *badger.DB
cache map[string]*Peer
}
// open opens the Badger database. Caller must hold mu (write).
func (ps *PeerStorage) open() error {
if ps.DbFile == "" {
ps.DbFile = uuid.New().String()
GetConfig().GetIdentity().Save()
}
if ps.cache == nil {
ps.cache = make(map[string]*Peer)
}
opts := badger.DefaultOptions(filepath.Join(GetConfig().StoragePath, GetConfig().GetIdentity().Uuid, ps.DbFile))
opts.Logger = nil
var err error
ps.db, err = badger.Open(opts)
return err
}
// close closes the Badger database. Caller must hold mu (write).
func (ps *PeerStorage) close() {
ps.db.Close()
}
// StorePeer stores a peer in the Badger database with Peer.Uid as key.
func (ps *PeerStorage) StorePeer(peer *Peer) error {
ps.mu.Lock()
defer ps.mu.Unlock()
return ps.storePeerLocked(peer)
}
// storePeerLocked is StorePeer without acquiring the lock. Caller must hold mu (write).
func (ps *PeerStorage) storePeerLocked(peer *Peer) error {
if err := ps.open(); err != nil {
return err
}
defer ps.close()
jsonsrv, err := json.Marshal(peer)
if err != nil {
return err
}
password, err := GetConfig().GetMemPass()
if err != nil {
return err
}
if peer.dbPassword != "" {
password = peer.dbPassword
}
data, err := meowlib.SymEncrypt(password, jsonsrv)
if err != nil {
return err
}
shakey := sha256.Sum256([]byte(peer.Uid))
key := shakey[:]
ps.cache[peer.Uid] = peer
return ps.db.Update(func(txn *badger.Txn) error {
return txn.Set(key, data)
})
}
// LoadPeer loads a Peer from the Badger database with Peer.GetUid() as key.
func (ps *PeerStorage) LoadPeer(uid string, password string) (*Peer, error) {
ps.mu.Lock()
defer ps.mu.Unlock()
var peer Peer
if err := ps.open(); err != nil {
return nil, err
}
defer ps.close()
shakey := sha256.Sum256([]byte(uid))
key := shakey[:]
err := ps.db.View(func(txn *badger.Txn) error {
item, err := txn.Get(key)
if err != nil {
return err
}
return item.Value(func(val []byte) error {
jsonsrv, err := meowlib.SymDecrypt(password, val)
if err != nil {
return err
}
return json.Unmarshal(jsonsrv, &peer)
})
})
return &peer, err
}
// DeletePeer deletes a Peer from the Badger database with Peer.GetUid() as key.
func (ps *PeerStorage) DeletePeer(uid string) error {
ps.mu.Lock()
defer ps.mu.Unlock()
if err := ps.open(); err != nil {
return err
}
defer ps.close()
shakey := sha256.Sum256([]byte(uid))
key := shakey[:]
err := ps.db.Update(func(txn *badger.Txn) error {
return txn.Delete(key)
})
if err == nil {
delete(ps.cache, uid)
}
return err
}
// LoadPeers loads all Peers from the Badger database and populates the cache.
func (ps *PeerStorage) LoadPeers(password string) ([]*Peer, error) {
ps.mu.Lock()
defer ps.mu.Unlock()
var peers []*Peer
if err := ps.open(); err != nil {
return nil, err
}
defer ps.close()
err := ps.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchSize = 10
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
var sc Peer
err := item.Value(func(val []byte) error {
jsonsrv, err := meowlib.SymDecrypt(password, val)
if err != nil {
return err
}
return json.Unmarshal(jsonsrv, &sc)
})
if err == nil {
peers = append(peers, &sc)
ps.cache[sc.Uid] = &sc
}
}
return nil
})
sort.Slice(peers, func(i, j int) bool {
return peers[i].Name < peers[j].Name
})
return peers, err
}
// GetPeers returns all peers from the cache as a sorted slice.
func (ps *PeerStorage) GetPeers() ([]*Peer, error) {
ps.mu.RLock()
defer ps.mu.RUnlock()
peers := make([]*Peer, 0, len(ps.cache))
for _, peer := range ps.cache {
peers = append(peers, peer)
}
sort.Slice(peers, func(i, j int) bool {
return peers[i].Name < peers[j].Name
})
return peers, nil
}
func (ps *PeerStorage) GetFromPublicKey(publickey string) *Peer {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, peer := range ps.cache {
if peer.ContactPublicKey == publickey {
return peer
}
}
return nil
}
func (ps *PeerStorage) GetFromInvitationId(invitationId string) *Peer {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, peer := range ps.cache {
if peer.InvitationId == invitationId {
return peer
}
}
return nil
}
func (ps *PeerStorage) GetFromMyLookupKey(publickey string) *Peer {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, peer := range ps.cache {
if peer.MyLookupKp.Public == publickey {
return peer
}
}
return nil
}
func (ps *PeerStorage) NameExists(name string) bool {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, peer := range ps.cache {
if peer.Name == name {
return true
}
}
return false
}
func (ps *PeerStorage) GetFromName(name string) *Peer {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, peer := range ps.cache {
if peer.Name == name {
return peer
}
}
return nil
}
func (ps *PeerStorage) GetFromUid(uid string) *Peer {
ps.mu.RLock()
defer ps.mu.RUnlock()
return ps.cache[uid]
}
// CheckInvitation checks if the received contact card is an answer to an invitation.
func (ps *PeerStorage) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string, invitationMessage string) {
ps.mu.RLock()
defer ps.mu.RUnlock()
for _, p := range ps.cache {
if p.InvitationId == ReceivedContact.InvitationId {
return true, p.Name, ReceivedContact.Name, ReceivedContact.InvitationMessage
}
}
return false, "", ReceivedContact.Name, ReceivedContact.InvitationMessage
}
// FinalizeInvitation completes an invitation handshake and persists the updated peer.
func (ps *PeerStorage) FinalizeInvitation(ReceivedContact *meowlib.ContactCard) error {
ps.mu.Lock()
defer ps.mu.Unlock()
for i, p := range ps.cache {
if p.InvitationId == ReceivedContact.InvitationId {
ps.cache[i].ContactEncryption = ReceivedContact.EncryptionPublicKey
ps.cache[i].ContactLookupKey = ReceivedContact.LookupPublicKey
ps.cache[i].ContactPublicKey = ReceivedContact.ContactPublicKey
if ps.cache[i].MySymKey == "" {
ps.cache[i].MySymKey = ReceivedContact.SymetricKey
}
srvs := []string{}
for srv := range ReceivedContact.PullServers {
srvs = append(srvs, ReceivedContact.PullServers[srv].GetUid())
}
ps.cache[i].ContactPullServers = srvs
return ps.storePeerLocked(ps.cache[i])
}
}
return errors.New("no matching contact found for invitationId " + ReceivedContact.InvitationId)
}