github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/p2p/profile_service.go (about)

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/libp2p/go-libp2p-core/host"
    10  	"github.com/libp2p/go-libp2p-core/network"
    11  	"github.com/libp2p/go-libp2p-core/peer"
    12  	protocol "github.com/libp2p/go-libp2p-core/protocol"
    13  	"github.com/qri-io/qri/config"
    14  	"github.com/qri-io/qri/event"
    15  	p2putil "github.com/qri-io/qri/p2p/p2putil"
    16  	"github.com/qri-io/qri/profile"
    17  )
    18  
    19  const (
    20  	// ProfileProtocolID is the protocol id for the profile exchange service
    21  	ProfileProtocolID = protocol.ID("/qri/profile/0.1.0")
    22  	// ProfileTimeout is the length of time we will wait for a response in a
    23  	// profile exchange
    24  	ProfileTimeout = time.Minute * 2
    25  )
    26  
    27  var (
    28  	// expectedProtocols is the list of protocols we expect a node that has a
    29  	// profile service to speak
    30  	expectedProtocols = []string{
    31  		string(QriProtocolID),
    32  		string(ProfileProtocolID),
    33  	}
    34  	// ErrPeerNotFound is returned when the profile service cannot find the
    35  	// peer in question
    36  	ErrPeerNotFound = fmt.Errorf("peer not found")
    37  )
    38  
    39  // QriProfileService manages the profile exchange. This exchange should happen
    40  // whenever a node connects to a new peer
    41  type QriProfileService struct {
    42  	host host.Host
    43  	pub  event.Publisher
    44  	// profiles is a store that has access to all the profiles we have
    45  	// ever seen on this node
    46  	profiles profile.Store
    47  	// peersMu is the mutext lock for the peers map
    48  	peersMu *sync.Mutex
    49  	// peers is a map of peers to a channel
    50  	// that channel is closed once the profile has been received
    51  	// it only tracks peers we are currently connected to
    52  	peers map[peer.ID]chan struct{}
    53  }
    54  
    55  // NewQriProfileService creates an profile exchange service
    56  func NewQriProfileService(profiles profile.Store, p event.Publisher) *QriProfileService {
    57  	q := &QriProfileService{
    58  		pub:      p,
    59  		profiles: profiles,
    60  		peersMu:  &sync.Mutex{},
    61  		peers:    map[peer.ID]chan struct{}{},
    62  	}
    63  
    64  	return q
    65  }
    66  
    67  // ConnectedQriPeers returns a list of currently connected peers
    68  func (q *QriProfileService) ConnectedQriPeers() []peer.ID {
    69  	q.peersMu.Lock()
    70  	defer q.peersMu.Unlock()
    71  	peers := []peer.ID{}
    72  	for pid := range q.peers {
    73  		peers = append(peers, pid)
    74  	}
    75  	return peers
    76  }
    77  
    78  // HandleQriPeerDisconnect checks if a given peer is a qri peer.
    79  // If so, it will wait until all profile exchanging has finished
    80  // Then remove the peer from the peers map, as well as publish
    81  // that the peer has disconnected.
    82  func (q *QriProfileService) HandleQriPeerDisconnect(pid peer.ID) {
    83  	q.peersMu.Lock()
    84  	wait, ok := q.peers[pid]
    85  	q.peersMu.Unlock()
    86  	if !ok {
    87  		return
    88  	}
    89  	<-wait
    90  
    91  	go func() {
    92  		ctx := context.Background()
    93  		pro, err := q.profiles.PeerProfile(ctx, pid)
    94  		if err != nil {
    95  			log.Debugf("error getting peer's profile. pid=%q err=%q", pid, err)
    96  		}
    97  		if err := q.pub.Publish(ctx, event.ETP2PQriPeerDisconnected, pro); err != nil {
    98  			log.Debugf("error publishing ETP2PQriPeerDisconnected event. pid=%q err=%q", pid, err)
    99  		}
   100  	}()
   101  
   102  	q.peersMu.Lock()
   103  	delete(q.peers, pid)
   104  	q.peersMu.Unlock()
   105  }
   106  
   107  // ConnectedPeerProfile returns a profile if that peer id refers to a peer that
   108  // we have connected to this session.
   109  func (q *QriProfileService) ConnectedPeerProfile(pid peer.ID) *profile.Profile {
   110  	q.peersMu.Lock()
   111  	_, ok := q.peers[pid]
   112  	q.peersMu.Unlock()
   113  	if !ok {
   114  		return nil
   115  	}
   116  	pro, err := q.profiles.PeerProfile(context.Background(), pid)
   117  	if err != nil {
   118  		log.Debugf("error getting peer profile: pid=%q err=%q", pid, err)
   119  		return nil
   120  	}
   121  	return pro
   122  }
   123  
   124  // Start adds a profile handler to the host, retains a local reference to the host
   125  func (q *QriProfileService) Start(h host.Host) {
   126  	q.host = h
   127  	h.SetStreamHandler(ProfileProtocolID, q.ProfileHandler)
   128  }
   129  
   130  // ProfileHandler listens for profile requests
   131  // it sends it's node's profile on the given stream
   132  // whenever a request comes in
   133  func (q *QriProfileService) ProfileHandler(s network.Stream) {
   134  	p := s.Conn().RemotePeer()
   135  
   136  	// close the stream, and wait for the other end of the stream to close as well
   137  	// this won't close the underlying connection
   138  	defer s.Close()
   139  
   140  	log.Debugf("%s received a profile request from %s %s", ProfileProtocolID, p, s.Conn().RemoteMultiaddr())
   141  
   142  	pro := q.profiles.Owner(context.Background())
   143  	if err := sendProfile(s, pro); err != nil {
   144  		log.Debugf("%s error sending profile to %s: %s", ProfileProtocolID, p, err)
   145  		return
   146  	}
   147  }
   148  
   149  // QriProfileRequest determine if the remote peer speaks the qri protocol
   150  // if it does, it protects the connection and sends a request for the
   151  // QriIdentifyService to get the peer's qri profile information
   152  func (q *QriProfileService) QriProfileRequest(ctx context.Context, pid peer.ID) error {
   153  	protocols, err := q.host.Peerstore().SupportsProtocols(pid, expectedProtocols...)
   154  	if err != nil {
   155  		log.Debugf("error examining the protocols for peer %s: %w", pid, err)
   156  		return fmt.Errorf("error examining the protocols for peer %s: %w", pid, err)
   157  	}
   158  
   159  	if len(protocols) == len(expectedProtocols) {
   160  		log.Debugf("peer %q does not speak the expected qri protocols", pid)
   161  		return fmt.Errorf("peer %q does not speak the expected qri protocols", pid)
   162  	}
   163  
   164  	// protect the connection from pruning
   165  	q.host.ConnManager().Protect(pid, qriSupportKey)
   166  	// get the peer's profile information
   167  	<-q.profileWait(ctx, pid)
   168  	return nil
   169  }
   170  
   171  // ProfileWait checks to see if a request to this peer is already in progress
   172  // if not, it sends a profile request to the peer
   173  // either way it returns a channel to wait on until the profile request has
   174  // been completed
   175  func (q *QriProfileService) profileWait(ctx context.Context, p peer.ID) <-chan struct{} {
   176  	log.Debugf("%s initiating peer profile request to %s", ProfileProtocolID, p)
   177  	q.peersMu.Lock()
   178  	wait, found := q.peers[p]
   179  	q.peersMu.Unlock()
   180  
   181  	if found {
   182  		log.Debugf("%s profile request to %s has already occurred", ProfileProtocolID, p)
   183  		return wait
   184  	}
   185  
   186  	q.peersMu.Lock()
   187  	defer q.peersMu.Unlock()
   188  
   189  	wait, found = q.peers[p]
   190  
   191  	if !found {
   192  		wait = make(chan struct{})
   193  		q.peers[p] = wait
   194  
   195  		go q.profileRequest(ctx, p, wait)
   196  	}
   197  
   198  	return wait
   199  }
   200  
   201  // profileRequest requests creates a stream, adds the ProfileProtocol
   202  // then it handles the profile response from the peer
   203  func (q *QriProfileService) profileRequest(ctx context.Context, pid peer.ID, signal chan struct{}) {
   204  	var err error
   205  
   206  	defer func() {
   207  		close(signal)
   208  		if err == nil {
   209  			pro, err := q.profiles.PeerProfile(ctx, pid)
   210  			if err != nil {
   211  				log.Debugf("error getting profile from profile store: %s", err)
   212  				return
   213  			}
   214  			go func() {
   215  				if err := q.pub.Publish(ctx, event.ETP2PQriPeerConnected, pro); err != nil {
   216  					log.Debugf("error publishing ETP2PQriPeerConnected event. pid=%q err=%q", pid, err)
   217  				}
   218  			}()
   219  		}
   220  	}()
   221  
   222  	s, err := q.host.NewStream(ctx, pid, ProfileProtocolID)
   223  	if err != nil {
   224  		log.Debugf("error opening profile stream to %q: %s", pid, err)
   225  		return
   226  	}
   227  
   228  	q.receiveAndStoreProfile(ctx, s)
   229  	return
   230  }
   231  
   232  // receiveAndStoreProfile takes a stream, receives a profile off the stream,
   233  // and stores it in the Repo's ProfileStore
   234  func (q *QriProfileService) receiveAndStoreProfile(ctx context.Context, s network.Stream) {
   235  	// helpers.FullClose will close the stream from this end and wait until the other
   236  	// end has also closed
   237  	// This closes the stream not the underlying connection
   238  	defer s.Close()
   239  
   240  	pro, err := receiveProfile(s)
   241  	if err != nil {
   242  		log.Errorf("%s error reading profile message from %q: %s", s.Protocol(), s.Conn().RemotePeer(), err)
   243  		return
   244  	}
   245  
   246  	log.Debugf("%s received profile message from %q %s", s.Protocol(), s.Conn().RemotePeer(), s.Conn().RemoteMultiaddr())
   247  
   248  	if err := q.profiles.PutProfile(ctx, pro); err != nil {
   249  		log.Debugw("putting received profile in store", "err", err)
   250  	}
   251  	return
   252  }
   253  
   254  func sendProfile(s network.Stream, pro *profile.Profile) error {
   255  	ws := p2putil.WrapStream(s)
   256  
   257  	pod, err := pro.Encode()
   258  	if err != nil {
   259  		return fmt.Errorf("error encoding profile.Profile to config.ProfilePod: %s", err)
   260  	}
   261  
   262  	if err := ws.Enc.Encode(&pod); err != nil {
   263  		return fmt.Errorf("error encoding profile to wrapped stream: %s", err)
   264  	}
   265  
   266  	if err := ws.W.Flush(); err != nil {
   267  		return fmt.Errorf("error flushing stream: %s", err)
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  func receiveProfile(s network.Stream) (*profile.Profile, error) {
   274  	ws := p2putil.WrapStream(s)
   275  	pod := &config.ProfilePod{}
   276  	if err := ws.Dec.Decode(&pod); err != nil {
   277  		return nil, fmt.Errorf("error decoding config.ProfilePod from wrapped stream: %s", err)
   278  	}
   279  	pro := &profile.Profile{}
   280  	if err := pro.Decode(pod); err != nil {
   281  		return nil, fmt.Errorf("error decoding Profile from config.ProfilePod: %s", err)
   282  	}
   283  	return pro, nil
   284  }