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  }