github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/logbook/logsync/p2p.go (about) 1 package logsync 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 10 host "github.com/libp2p/go-libp2p-core/host" 11 net "github.com/libp2p/go-libp2p-core/network" 12 peer "github.com/libp2p/go-libp2p-core/peer" 13 protocol "github.com/libp2p/go-libp2p-core/protocol" 14 "github.com/qri-io/dag/dsync/p2putil" 15 "github.com/qri-io/qri/auth/key" 16 "github.com/qri-io/qri/dsref" 17 "github.com/qri-io/qri/profile" 18 "github.com/qri-io/qri/repo" 19 reporef "github.com/qri-io/qri/repo/ref" 20 ) 21 22 const ( 23 // LogsyncProtocolID is the dsyc p2p Protocol Identifier 24 LogsyncProtocolID = protocol.ID("/qri/logsync") 25 // LogsyncServiceTag tags the type & version of the dsync service 26 LogsyncServiceTag = "qri/logsync/0.1.1-dev" 27 // default value to give logsync peer connections in connmanager, one hunnit 28 logsyncSupportValue = 100 29 ) 30 31 var ( 32 // mtGet identifies the "put" message type, a client pushing a log to a remote 33 mtPut = p2putil.MsgType("put") 34 // mtGet identifies the "get" message type, a request for a log 35 mtGet = p2putil.MsgType("get") 36 // mtDel identifies the "del" message type, a request to remove a log 37 mtDel = p2putil.MsgType("del") 38 ) 39 40 type p2pClient struct { 41 remotePeerID peer.ID 42 *p2pHandler 43 } 44 45 // assert at compile time that p2pClient implements DagSyncable 46 var _ remote = (*p2pClient)(nil) 47 48 func (c *p2pClient) addr() string { 49 return c.remotePeerID.Pretty() 50 } 51 52 func (c *p2pClient) put(ctx context.Context, author profile.Author, ref dsref.Ref, r io.Reader) (err error) { 53 data, err := ioutil.ReadAll(r) 54 if err != nil { 55 return err 56 } 57 58 headers := []string{ 59 "phase", "request", 60 "ref", ref.String(), 61 } 62 headers, err = addAuthorP2PHeaders(headers, author) 63 if err != nil { 64 return err 65 } 66 msg := p2putil.NewMessage(c.host.ID(), mtPut, data).WithHeaders(headers...) 67 _, err = c.sendMessage(ctx, msg, c.remotePeerID) 68 return err 69 } 70 71 func (c *p2pClient) get(ctx context.Context, author profile.Author, ref dsref.Ref) (sender profile.Author, data io.Reader, err error) { 72 headers := []string{ 73 "phase", "request", 74 "ref", ref.String(), 75 } 76 headers, err = addAuthorP2PHeaders(headers, author) 77 if err != nil { 78 return nil, nil, err 79 } 80 81 msg := p2putil.NewMessage(c.host.ID(), mtGet, nil).WithHeaders(headers...) 82 83 res, err := c.sendMessage(ctx, msg, c.remotePeerID) 84 if err != nil { 85 return nil, nil, err 86 } 87 88 sender, err = authorFromP2PHeaders(res) 89 return sender, bytes.NewReader(res.Body), err 90 } 91 92 func (c *p2pClient) del(ctx context.Context, author profile.Author, ref dsref.Ref) error { 93 headers := []string{ 94 "phase", "request", 95 "ref", ref.String(), 96 } 97 headers, err := addAuthorP2PHeaders(headers, author) 98 if err != nil { 99 return err 100 } 101 102 msg := p2putil.NewMessage(c.host.ID(), mtDel, nil).WithHeaders(headers...) 103 _, err = c.sendMessage(ctx, msg, c.remotePeerID) 104 return err 105 } 106 107 func addAuthorP2PHeaders(h []string, author profile.Author) ([]string, error) { 108 pubKey, err := key.EncodePubKeyB64(author.AuthorPubKey()) 109 if err != nil { 110 return nil, err 111 } 112 return append(h, "author_id", author.AuthorID(), "pub_key", pubKey, "author_username", author.Username()), nil 113 } 114 115 func authorFromP2PHeaders(msg p2putil.Message) (profile.Author, error) { 116 pub, err := key.DecodeB64PubKey(msg.Header("pub_key")) 117 if err != nil { 118 return nil, fmt.Errorf("decoding public key: %s", err) 119 } 120 121 return profile.NewAuthor(msg.Header("author_id"), pub, msg.Header("author_username")), nil 122 } 123 124 // p2pHandler implements logsync as a libp2p protocol handler 125 type p2pHandler struct { 126 logsync *Logsync 127 host host.Host 128 handlers map[p2putil.MsgType]p2putil.HandlerFunc 129 } 130 131 // newp2pHandler creates a p2p remote stream handler from a dsync.Remote 132 func newp2pHandler(logsync *Logsync, host host.Host) *p2pHandler { 133 c := &p2pHandler{logsync: logsync, host: host} 134 c.handlers = map[p2putil.MsgType]p2putil.HandlerFunc{ 135 mtPut: c.HandlePut, 136 mtGet: c.HandleGet, 137 mtDel: c.HandleDel, 138 } 139 140 go host.SetStreamHandler(LogsyncProtocolID, c.LibP2PStreamHandler) 141 return c 142 } 143 144 // LibP2PStreamHandler provides remote access over p2p 145 func (c *p2pHandler) LibP2PStreamHandler(s net.Stream) { 146 c.handleStream(p2putil.WrapStream(s), nil) 147 } 148 149 // HandlePut requests a new send session from the remote, which will return 150 // a delta manifest of blocks the remote needs and a session id that must 151 // be sent with each block 152 func (c *p2pHandler) HandlePut(ws *p2putil.WrappedStream, msg p2putil.Message) (hangup bool) { 153 if msg.Header("phase") == "request" { 154 ctx := context.Background() 155 author, err := authorFromP2PHeaders(msg) 156 if err != nil { 157 return true 158 } 159 160 ref, err := dsref.Parse(msg.Header("ref")) 161 if err != nil { 162 return true 163 } 164 165 if err = c.logsync.put(ctx, author, ref, bytes.NewReader(msg.Body)); err != nil { 166 return true 167 } 168 169 headers := []string{ 170 "phase", "response", 171 } 172 headers, err = addAuthorP2PHeaders(headers, c.logsync.Author()) 173 if err != nil { 174 return true 175 } 176 177 res := msg.WithHeaders(headers...) 178 if err := ws.SendMessage(res); err != nil { 179 return true 180 } 181 } 182 return true 183 } 184 185 // HandleGet places a block on the remote 186 func (c *p2pHandler) HandleGet(ws *p2putil.WrappedStream, msg p2putil.Message) (hangup bool) { 187 if msg.Header("phase") == "request" { 188 ctx := context.Background() 189 author, err := authorFromP2PHeaders(msg) 190 if err != nil { 191 return true 192 } 193 194 ref, err := repo.ParseDatasetRef(msg.Header("ref")) 195 if err != nil { 196 return true 197 } 198 199 sender, r, err := c.logsync.get(ctx, author, reporef.ConvertToDsref(ref)) 200 if err != nil { 201 return true 202 } 203 204 data, err := ioutil.ReadAll(r) 205 if err != nil { 206 return true 207 } 208 209 headers := []string{ 210 "phase", "response", 211 } 212 headers, err = addAuthorP2PHeaders(headers, sender) 213 if err != nil { 214 return true 215 } 216 217 res := msg.WithHeaders(headers...).Update(data) 218 if err := ws.SendMessage(res); err != nil { 219 return true 220 } 221 } 222 223 return true 224 } 225 226 // HandleDel asks the remote for a manifest specified by the root ID of a DAG 227 func (c *p2pHandler) HandleDel(ws *p2putil.WrappedStream, msg p2putil.Message) (hangup bool) { 228 if msg.Header("phase") == "request" { 229 ctx := context.Background() 230 author, err := authorFromP2PHeaders(msg) 231 if err != nil { 232 return true 233 } 234 235 ref, err := repo.ParseDatasetRef(msg.Header("ref")) 236 if err != nil { 237 return true 238 } 239 240 if err = c.logsync.del(ctx, author, reporef.ConvertToDsref(ref)); err != nil { 241 return true 242 } 243 244 res := msg.WithHeaders("phase", "response") 245 if err := ws.SendMessage(res); err != nil { 246 return true 247 } 248 } 249 return true 250 } 251 252 // sendMessage opens a stream & sends a message to a peer id 253 func (c *p2pHandler) sendMessage(ctx context.Context, msg p2putil.Message, pid peer.ID) (p2putil.Message, error) { 254 s, err := c.host.NewStream(ctx, pid, LogsyncProtocolID) 255 if err != nil { 256 return p2putil.Message{}, fmt.Errorf("error opening stream: %s", err.Error()) 257 } 258 defer s.Close() 259 260 // now that we have a confirmed working connection 261 // tag this peer as supporting the qri protocol in the connection manager 262 // rem.host.ConnManager().TagPeer(pid, logsyncSupportKey, logsyncSupportValue) 263 264 ws := p2putil.WrapStream(s) 265 replies := make(chan p2putil.Message) 266 go c.handleStream(ws, replies) 267 if err := ws.SendMessage(msg); err != nil { 268 return p2putil.Message{}, err 269 } 270 271 reply := <-replies 272 return reply, nil 273 } 274 275 // handleStream is a loop which receives and handles messages 276 // When Message.HangUp is true, it exits. This will close the stream 277 // on one of the sides. The other side's receiveMessage() will error 278 // with EOF, thus also breaking out from the loop. 279 func (c *p2pHandler) handleStream(ws *p2putil.WrappedStream, replies chan p2putil.Message) { 280 for { 281 // Loop forever, receiving messages until the other end hangs up 282 // or something goes wrong 283 msg, err := ws.ReceiveMessage() 284 285 if err != nil { 286 if err.Error() == "EOF" { 287 break 288 } 289 // oplog.Debugf("error receiving message: %s", err.Error()) 290 break 291 } 292 293 if replies != nil { 294 go func() { replies <- msg }() 295 } 296 297 handler, ok := c.handlers[msg.Type] 298 if !ok { 299 break 300 } 301 302 // call the handler, if it returns true, we hangup 303 if handler(ws, msg) { 304 break 305 } 306 } 307 308 ws.Close() 309 }