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 }