package peerbroker import ( "context" "errors" "io" "reflect" "github.com/pion/webrtc/v3" "golang.org/x/xerrors" "github.com/coder/coder/peer" "github.com/coder/coder/peerbroker/proto" ) // Dial consumes the PeerBroker gRPC connection negotiation stream to produce a WebRTC peered connection. func Dial(stream proto.DRPCPeerBroker_NegotiateConnectionClient, iceServers []webrtc.ICEServer, opts *peer.ConnOptions) (*peer.Conn, error) { peerConn, err := peer.Client(iceServers, opts) if err != nil { return nil, xerrors.Errorf("create peer connection: %w", err) } go func() { defer stream.Close() // Exchanging messages from the peer connection to negotiate a connection. for { select { case <-peerConn.Closed(): return case sessionDescription := <-peerConn.LocalSessionDescription(): err = stream.Send(&proto.Exchange{ Message: &proto.Exchange_Sdp{ Sdp: &proto.WebRTCSessionDescription{ SdpType: int32(sessionDescription.Type), Sdp: sessionDescription.SDP, }, }, }) if err != nil { _ = peerConn.CloseWithError(xerrors.Errorf("send local session description: %w", err)) return } case iceCandidate := <-peerConn.LocalCandidate(): err = stream.Send(&proto.Exchange{ Message: &proto.Exchange_IceCandidate{ IceCandidate: iceCandidate.Candidate, }, }) if err != nil { _ = peerConn.CloseWithError(xerrors.Errorf("send local candidate: %w", err)) return } } } }() go func() { // Exchanging messages from the server to negotiate a connection. for { serverToClientMessage, err := stream.Recv() if err != nil { // p2p connections should never die if this stream does due // to proper closure or context cancellation! if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) { return } _ = peerConn.CloseWithError(xerrors.Errorf("recv: %w", err)) return } switch { case serverToClientMessage.GetSdp() != nil: peerConn.SetRemoteSessionDescription(webrtc.SessionDescription{ Type: webrtc.SDPType(serverToClientMessage.GetSdp().SdpType), SDP: serverToClientMessage.GetSdp().Sdp, }) case serverToClientMessage.GetIceCandidate() != "": peerConn.AddRemoteCandidate(webrtc.ICECandidateInit{ Candidate: serverToClientMessage.GetIceCandidate(), }) default: _ = peerConn.CloseWithError(xerrors.Errorf("unhandled message: %s", reflect.TypeOf(serverToClientMessage).String())) return } } }() return peerConn, nil }