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) }