func TestHostProtoPreference(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() h1, h2 := getHostPair(ctx, t) defer h1.Close() defer h2.Close() protoOld := protocol.ID("/testing") protoNew := protocol.ID("/testing/1.1.0") protoMinor := protocol.ID("/testing/1.2.0") connectedOn := make(chan protocol.ID, 16) handler := func(s inet.Stream) { connectedOn <- s.Protocol() s.Close() } h1.SetStreamHandler(protoOld, handler) s, err := h2.NewStream(ctx, h1.ID(), protoMinor, protoNew, protoOld) if err != nil { t.Fatal(err) } assertWait(t, connectedOn, protoOld) s.Close() mfunc, err := host.MultistreamSemverMatcher(protoMinor) if err != nil { t.Fatal(err) } h1.SetStreamHandlerMatch(protoMinor, mfunc, handler) // remembered preference will be chosen first, even when the other side newly supports it s2, err := h2.NewStream(ctx, h1.ID(), protoMinor, protoNew, protoOld) if err != nil { t.Fatal(err) } // required to force 'lazy' handshake _, err = s2.Write([]byte("hello")) if err != nil { t.Fatal(err) } assertWait(t, connectedOn, protoOld) s2.Close() s3, err := h2.NewStream(ctx, h1.ID(), protoMinor) if err != nil { t.Fatal(err) } assertWait(t, connectedOn, protoMinor) s3.Close() }
// NewStream opens a new stream to given peer p, and writes a p2p/protocol // header with given protocol.ID. If there is no connection to p, attempts // to create one. If ProtocolID is "", writes no header. // (Threadsafe) func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (inet.Stream, error) { pref, err := h.preferredProtocol(p, pids) if err != nil { return nil, err } if pref != "" { return h.newStream(ctx, p, pref) } var protoStrs []string for _, pid := range pids { protoStrs = append(protoStrs, string(pid)) } s, err := h.Network().NewStream(ctx, p) if err != nil { return nil, err } selected, err := msmux.SelectOneOf(protoStrs, s) if err != nil { s.Close() return nil, err } selpid := protocol.ID(selected) s.SetProtocol(selpid) h.Peerstore().AddProtocols(p, selected) return mstream.WrapStream(s, h.bwc), nil }
// newStreamHandler is the remote-opened stream handler for inet.Network // TODO: this feels a bit wonky func (h *BasicHost) newStreamHandler(s inet.Stream) { before := time.Now() protoID, handle, err := h.Mux().Negotiate(s) took := time.Now().Sub(before) if err != nil { if err == io.EOF { logf := log.Debugf if took > time.Second*10 { logf = log.Warningf } logf("protocol EOF: %s (took %s)", s.Conn().RemotePeer(), took) } else { log.Warning("protocol mux failed: %s (took %s)", err, took) } return } s.SetProtocol(protocol.ID(protoID)) if h.bwc != nil { s = mstream.WrapStream(s, h.bwc) } log.Debugf("protocol negotiation took %s", took) go handle(protoID, s) }
// SetStreamHandlerMatch sets the protocol handler on the Host's Mux // using a matching function to do protocol comparisons func (h *BasicHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler inet.StreamHandler) { h.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error { is := rwc.(inet.Stream) is.SetProtocol(protocol.ID(p)) handler(is) return nil }) }
func (h *BasicHost) preferredProtocol(p peer.ID, pids []protocol.ID) (protocol.ID, error) { pidstrs := pidsToStrings(pids) supported, err := h.Peerstore().SupportsProtocols(p, pidstrs...) if err != nil { return "", err } var out protocol.ID if len(supported) > 0 { out = protocol.ID(supported[0]) } return out, nil }
// newStreamHandler is the remote-opened stream handler for inet.Network // TODO: this feels a bit wonky func (h *BasicHost) newStreamHandler(s inet.Stream) { before := time.Now() if h.NegotiateTimeout != 0 { if err := s.SetDeadline(time.Now().Add(h.NegotiateTimeout)); err != nil { log.Error("setting stream deadline: ", err) s.Close() return } } protoID, handle, err := h.Mux().Negotiate(s) took := time.Now().Sub(before) if err != nil { if err == io.EOF { logf := log.Debugf if took > time.Second*10 { logf = log.Warningf } logf("protocol EOF: %s (took %s)", s.Conn().RemotePeer(), took) } else { log.Warning("protocol mux failed: %s (took %s)", err, took) } s.Close() return } if h.NegotiateTimeout != 0 { if err := s.SetDeadline(time.Time{}); err != nil { log.Error("resetting stream deadline: ", err) s.Close() return } } s.SetProtocol(protocol.ID(protoID)) if h.bwc != nil { s = mstream.WrapStream(s, h.bwc) } log.Debugf("protocol negotiation took %s", took) go handle(protoID, s) }