github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/lib/peers.go (about) 1 package lib 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/qri-io/qri/base/params" 9 "github.com/qri-io/qri/config" 10 qhttp "github.com/qri-io/qri/lib/http" 11 "github.com/qri-io/qri/p2p" 12 "github.com/qri-io/qri/profile" 13 "github.com/qri-io/qri/repo" 14 15 peer "github.com/libp2p/go-libp2p-core/peer" 16 ma "github.com/multiformats/go-multiaddr" 17 ) 18 19 // PeerMethods extends a lib.Instance with business logic for peer-to-peer 20 // interaction 21 type PeerMethods struct { 22 d dispatcher 23 } 24 25 // Name returns the name of this method group 26 func (m PeerMethods) Name() string { return "peer" } 27 28 // Attributes defines attributes for each method 29 func (m PeerMethods) Attributes() map[string]AttributeSet { 30 return map[string]AttributeSet{ 31 "list": {Endpoint: qhttp.AEPeers, HTTPVerb: "POST"}, 32 "info": {Endpoint: qhttp.AEPeer, HTTPVerb: "POST"}, 33 "connect": {Endpoint: qhttp.AEConnect, HTTPVerb: "POST"}, 34 "disconnect": {Endpoint: qhttp.AEDisconnect, HTTPVerb: "POST"}, 35 "connections": {Endpoint: qhttp.AEConnections, HTTPVerb: "POST"}, 36 "connectedqriprofiles": {Endpoint: qhttp.AEConnectedQriProfiles, HTTPVerb: "POST"}, 37 } 38 } 39 40 // PeerListParams defines parameters for the List method 41 type PeerListParams struct { 42 params.List 43 // Cached == true will return offline peers from the repo 44 // as well as online peers, default is to list connected peers only 45 Cached bool `json:"cached"` 46 } 47 48 // SetNonZeroDefaults sets a default limit and offset 49 func (p *PeerListParams) SetNonZeroDefaults() { 50 if p.Offset < 0 { 51 p.Offset = 0 52 } 53 if p.Limit <= 0 { 54 p.Limit = params.DefaultListLimit 55 } 56 } 57 58 // List lists Peers on the qri network 59 func (m PeerMethods) List(ctx context.Context, p *PeerListParams) ([]*config.ProfilePod, error) { 60 got, _, err := m.d.Dispatch(ctx, dispatchMethodName(m, "list"), p) 61 if res, ok := got.([]*config.ProfilePod); ok { 62 return res, err 63 } 64 return nil, dispatchReturnError(got, err) 65 } 66 67 // PeerInfoParams defines parameters for the Info method 68 type PeerInfoParams struct { 69 Peername string `json:"peername"` 70 ProfileID string `json:"profileID"` 71 // Verbose adds network details from the p2p Peerstore 72 Verbose bool `json:"verbose"` 73 } 74 75 // Info shows peer profile details 76 func (m PeerMethods) Info(ctx context.Context, p *PeerInfoParams) (*config.ProfilePod, error) { 77 got, _, err := m.d.Dispatch(ctx, dispatchMethodName(m, "info"), p) 78 if res, ok := got.(*config.ProfilePod); ok { 79 return res, err 80 } 81 return nil, dispatchReturnError(got, err) 82 } 83 84 // Connect attempts to create a connection with a peer for a given peer.ID 85 func (m PeerMethods) Connect(ctx context.Context, p *ConnectParamsPod) (*config.ProfilePod, error) { 86 got, _, err := m.d.Dispatch(ctx, dispatchMethodName(m, "connect"), p) 87 if res, ok := got.(*config.ProfilePod); ok { 88 return res, err 89 } 90 return nil, dispatchReturnError(got, err) 91 } 92 93 // Disconnect explicitly closes a peer connection 94 func (m PeerMethods) Disconnect(ctx context.Context, p *ConnectParamsPod) error { 95 _, _, err := m.d.Dispatch(ctx, dispatchMethodName(m, "disconnect"), p) 96 return err 97 } 98 99 // ConnectionsParams defines parameters for the Connections method 100 type ConnectionsParams struct { 101 params.List 102 } 103 104 // SetNonZeroDefaults sets a default limit and offset 105 func (p *ConnectionsParams) SetNonZeroDefaults() { 106 if p.Offset < 0 { 107 p.Offset = 0 108 } 109 if p.Limit <= 0 { 110 p.Limit = params.DefaultListLimit 111 } 112 } 113 114 // Connections lists PeerID's we're currently connected to. If running 115 // IPFS this will also return connected IPFS nodes 116 func (m PeerMethods) Connections(ctx context.Context, p *ConnectionsParams) ([]string, error) { 117 got, _, err := m.d.Dispatch(ctx, dispatchMethodName(m, "connections"), p) 118 if res, ok := got.([]string); ok { 119 return res, err 120 } 121 return nil, dispatchReturnError(got, err) 122 } 123 124 // ConnectedQriProfiles lists profiles we're currently connected to 125 func (m PeerMethods) ConnectedQriProfiles(ctx context.Context, p *ConnectionsParams) ([]*config.ProfilePod, error) { 126 got, _, err := m.d.Dispatch(ctx, dispatchMethodName(m, "connectedqriprofiles"), p) 127 if res, ok := got.([]*config.ProfilePod); ok { 128 return res, err 129 } 130 return nil, dispatchReturnError(got, err) 131 } 132 133 // ConnectParamsPod defines parameters for defining a connection 134 // to a peer as plain-old-data 135 type ConnectParamsPod struct { 136 Peername string `json:"peername"` 137 ProfileID string `json:"profileID"` 138 NetworkID string `json:"networkID"` 139 Multiaddr string `json:"multiaddr"` 140 } 141 142 // NewConnectParamsPod attempts to turn a string into peer connection parameters 143 func NewConnectParamsPod(s string) *ConnectParamsPod { 144 pcpod := &ConnectParamsPod{} 145 146 if strings.HasPrefix(s, "/ipfs/") { 147 pcpod.NetworkID = s 148 } else if maddr, err := ma.NewMultiaddr(s); err == nil { 149 pcpod.Multiaddr = maddr.String() 150 } else if len(s) == 46 && strings.HasPrefix(s, "Qm") { 151 pcpod.ProfileID = s 152 } else { 153 pcpod.Peername = s 154 } 155 156 return pcpod 157 } 158 159 func (p ConnectParamsPod) String() string { 160 if p.Peername != "" { 161 return p.Peername 162 } 163 if p.ProfileID != "" { 164 return p.ProfileID 165 } 166 if p.Multiaddr != "" { 167 return p.Multiaddr 168 } 169 if p.NetworkID != "" { 170 return p.NetworkID 171 } 172 return "" 173 } 174 175 // Decode turns plain-old-data into it's rich types 176 func (p ConnectParamsPod) Decode() (cp p2p.PeerConnectionParams, err error) { 177 cp.Peername = p.Peername 178 179 if p.NetworkID != "" { 180 id := strings.TrimPrefix(p.NetworkID, "/ipfs/") 181 if len(id) == len(p.NetworkID) { 182 err = fmt.Errorf("network IDs must have a network prefix (eg. /ipfs/)") 183 return 184 } 185 if cp.PeerID, err = peer.IDB58Decode(id); err != nil { 186 err = fmt.Errorf("invalid networkID: %s", err.Error()) 187 return 188 } 189 } 190 191 if p.ProfileID != "" { 192 if cp.ProfileID, err = profile.IDB58Decode(p.ProfileID); err != nil { 193 err = fmt.Errorf("invalid profileID: %s", err.Error()) 194 return 195 } 196 } 197 198 if p.Multiaddr != "" { 199 if cp.Multiaddr, err = ma.NewMultiaddr(p.Multiaddr); err != nil { 200 err = fmt.Errorf("invalid multiaddr: %s", err.Error()) 201 } 202 } 203 204 return 205 } 206 207 // peerImpl holds the method implementations for PeerMethods 208 type peerImpl struct{} 209 210 // List lists Peers on the qri network 211 func (peerImpl) List(scope scope, p *PeerListParams) ([]*config.ProfilePod, error) { 212 res := []*config.ProfilePod{} 213 214 if scope.Node() == nil || !scope.Node().Online { 215 return nil, fmt.Errorf("error: not connected, run `qri connect` in another window") 216 } 217 218 var err error 219 220 // requesting user is hardcoded as node owner 221 u := scope.ActiveProfile() 222 res, err = p2p.ListPeers(scope.Context(), scope.Node(), u.ID, p.Offset, p.Limit, !p.Cached) 223 if err != nil { 224 return nil, err 225 } 226 return res, nil 227 } 228 229 // Info shows peer profile details 230 func (peerImpl) Info(scope scope, p *PeerInfoParams) (*config.ProfilePod, error) { 231 res := config.ProfilePod{} 232 // TODO: Move most / all of this to p2p package, perhaps. 233 r := scope.Repo() 234 235 var pid profile.ID 236 var err error 237 if p.ProfileID != "" { 238 pid, err = profile.IDB58Decode(p.ProfileID) 239 if err != nil { 240 return nil, err 241 } 242 } 243 244 profiles, err := r.Profiles().List(scope.Context()) 245 if err != nil { 246 log.Debug(err.Error()) 247 return nil, err 248 } 249 250 for _, pro := range profiles { 251 if pro.ID == pid || pro.Peername == p.Peername { 252 if p.Verbose && len(pro.PeerIDs) > 0 { 253 // TODO - grab more than just the first peerID 254 pinfo := scope.Node().PeerInfo(pro.PeerIDs[0]) 255 pro.NetworkAddrs = pinfo.Addrs 256 } 257 258 prof, err := pro.Encode() 259 if err != nil { 260 return nil, err 261 } 262 res = *prof 263 264 connected := scope.Node().ConnectedQriProfiles(scope.Context()) 265 266 // If the requested profileID is in the list of connected peers, set Online flag. 267 if _, ok := connected[pro.ID]; ok { 268 res.Online = true 269 } 270 // If the requested profileID is myself and I'm Online, set Online flag. 271 if peer.ID(pro.ID) == scope.Node().ID && scope.Node().Online { 272 res.Online = true 273 } 274 return &res, nil 275 } 276 } 277 278 return nil, repo.ErrNotFound 279 } 280 281 // Connect attempts to create a connection with a peer for a given peer.ID 282 func (peerImpl) Connect(scope scope, p *ConnectParamsPod) (*config.ProfilePod, error) { 283 pcp, err := p.Decode() 284 if err != nil { 285 return nil, err 286 } 287 288 prof, err := scope.Node().ConnectToPeer(scope.Context(), pcp) 289 if err != nil { 290 return nil, err 291 } 292 293 pro, err := prof.Encode() 294 if err != nil { 295 return nil, err 296 } 297 298 return pro, nil 299 } 300 301 // Disconnect explicitly closes a peer connection 302 func (peerImpl) Disconnect(scope scope, p *ConnectParamsPod) error { 303 pcp, err := p.Decode() 304 if err != nil { 305 return err 306 } 307 308 return scope.Node().DisconnectFromPeer(scope.Context(), pcp) 309 } 310 311 // Connections lists PeerID's we're currently connected to. If running 312 // IPFS this will also return connected IPFS nodes 313 func (peerImpl) Connections(scope scope, p *ConnectionsParams) ([]string, error) { 314 // TODO (ramfox): limit and offset not currently used 315 return scope.Node().ConnectedPeers(), nil 316 } 317 318 // ConnectedQriProfiles lists profiles we're currently connected to 319 func (peerImpl) ConnectedQriProfiles(scope scope, p *ConnectionsParams) ([]*config.ProfilePod, error) { 320 // TODO (ramfox): offset not currently used 321 connected := scope.Node().ConnectedQriProfiles(scope.Context()) 322 323 build := make([]*config.ProfilePod, intMin(len(connected), p.Limit)) 324 for _, pro := range connected { 325 build = append(build, pro) 326 if len(build) >= p.Limit { 327 break 328 } 329 } 330 return build, nil 331 } 332 333 func intMin(a, b int) int { 334 if a < b { 335 return a 336 } 337 return b 338 }