├── README.md ├── addr.go ├── addr_test.go ├── conn.go ├── conn_test.go ├── dial.go ├── examples └── http-signaler │ ├── README.md │ ├── main.go │ └── signaler.go ├── framer.go ├── framer_test.go ├── listener.go ├── listener_test.go ├── signaler.go ├── signaler_test.go └── test └── main.go /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | Pion DCNet 4 |
5 |

6 |

Net interfaces around WebRTC data channels.

7 |
8 | 9 | **Warning**: This package is a proof of concept. Feel free to play around, log issues or even contribute but don't rely on anything! 10 | 11 | Package DCNet augments the net.* interfaces over a WebRTC data channels. 12 | 13 | ### Usage 14 | The idea of the package is that you implement a ``Signaler`` that negotiates data channels and get a net.Listener and net.Conn on top of them. The following pseudo-code shows how this works: 15 | 16 | Listening side: 17 | ``` Go 18 | signaler := NewSignaler(someOptions) 19 | listener := dcnet.NewListener(signaler) 20 | for { 21 | conn, err := listener.Accept() 22 | check(err) 23 | _, err = conn.Write([]byte("Hallo")) 24 | check(err) 25 | } 26 | ``` 27 | 28 | Dialing side: 29 | ``` Go 30 | signaler := NewSignaler(someOptions) 31 | conn, err := dcnet.Dial(signaler) 32 | check(err) 33 | 34 | msg := make([]byte, 100) 35 | _, err = conn.Read(msg) 36 | check(err) 37 | fmt.Println(string(msg)) 38 | ``` 39 | ### Examples 40 | Please refer to the examples directory for working examples. -------------------------------------------------------------------------------- /addr.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | // NilAddr is an empty address. 4 | // It can be used in scenario's where multiple 5 | // peers are not needed. 6 | type NilAddr struct { 7 | ID string 8 | } 9 | 10 | func (a *NilAddr) Network() string { 11 | return "WebRTC" 12 | } 13 | 14 | func (a *NilAddr) String() string { 15 | return "" 16 | } 17 | 18 | // IDAddr identifies a peer by a simple ID. 19 | type IDAddr struct { 20 | ID string 21 | } 22 | 23 | func (a *IDAddr) Network() string { 24 | return "WebRTC" 25 | } 26 | 27 | func (a *IDAddr) String() string { 28 | return a.ID 29 | } 30 | 31 | // SessionAddr identifies a peer by a 32 | // combination of APIKey, RoomID and Session ID. 33 | type SessionAddr struct { 34 | APIKey string 35 | RoomID string 36 | SessionID string 37 | } 38 | 39 | func (a *SessionAddr) Network() string { 40 | return "WebRTC" 41 | } 42 | 43 | func (a *SessionAddr) String() string { 44 | return a.APIKey + 45 | "/" + a.RoomID + 46 | "/" + a.SessionID 47 | } 48 | -------------------------------------------------------------------------------- /addr_test.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import "net" 4 | 5 | // Interface assertions 6 | var _ net.Addr = (*NilAddr)(nil) 7 | var _ net.Addr = (*IDAddr)(nil) 8 | var _ net.Addr = (*SessionAddr)(nil) 9 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net" 8 | "time" 9 | 10 | "github.com/pions/webrtc" 11 | "github.com/pions/webrtc/pkg/datachannel" 12 | ) 13 | 14 | // NewConn creates a Conn around a data channel. The data channel is assumed to be open already. 15 | func NewConn(dc *webrtc.RTCDataChannel, laddr net.Addr, raddr net.Addr) (net.Conn, error) { 16 | r, w := io.Pipe() 17 | 18 | res := &Conn{ 19 | dc: dc, 20 | laddr: laddr, 21 | raddr: raddr, 22 | p: r, 23 | } 24 | 25 | go func() { 26 | dc.Lock() 27 | defer dc.Unlock() 28 | dc.Onmessage = func(payload datachannel.Payload) { 29 | switch p := payload.(type) { 30 | // case *datachannel.PayloadString: 31 | // fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), d.Label, string(p.Data)) 32 | case *datachannel.PayloadBinary: 33 | w.Write(p.Data) 34 | // default: 35 | // fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), d.Label) 36 | } 37 | } 38 | }() 39 | 40 | return res, nil 41 | } 42 | 43 | // Conn is a net.Conn over a datachannel 44 | type Conn struct { 45 | dc *webrtc.RTCDataChannel 46 | laddr net.Addr 47 | raddr net.Addr 48 | p *io.PipeReader 49 | isClosed bool 50 | } 51 | 52 | // Read reads data from the underlying the data channel 53 | func (c *Conn) Read(b []byte) (int, error) { 54 | if c.isClosed { 55 | return 0, errors.New("read on closed conn") 56 | } 57 | i, err := c.p.Read(b) 58 | return i, err 59 | } 60 | 61 | // Write writes the data to the underlying data channel 62 | func (c *Conn) Write(b []byte) (int, error) { 63 | if c.isClosed { 64 | return 0, errors.New("write on closed conn") 65 | } 66 | err := c.dc.Send(datachannel.PayloadBinary{Data: b}) 67 | if err != nil { 68 | return 0, err 69 | } 70 | return len(b), nil 71 | } 72 | 73 | // Close closes the datachannel and peerconnection 74 | func (c *Conn) Close() error { 75 | 76 | if c.isClosed { 77 | return errors.New("close on closed conn") 78 | } 79 | 80 | // TODO: Locking 81 | c.isClosed = true 82 | 83 | // Unblock readers 84 | err := c.p.Close() 85 | if err != nil { 86 | fmt.Println("failed to close pipe:", err) 87 | return err 88 | } 89 | 90 | // TODO: Implement datachannel closing procedure 91 | // c.dc.Close() 92 | // TODO: cleanup peerconnection 93 | return nil 94 | } 95 | 96 | func (c *Conn) LocalAddr() net.Addr { 97 | return c.laddr 98 | } 99 | 100 | func (c *Conn) RemoteAddr() net.Addr { 101 | return c.raddr 102 | } 103 | 104 | // SetDeadline 105 | func (c *Conn) SetDeadline(t time.Time) error { 106 | panic("TODO") 107 | return nil 108 | } 109 | 110 | // SetReadDeadline 111 | func (c *Conn) SetReadDeadline(t time.Time) error { 112 | panic("TODO") 113 | return nil 114 | 115 | } 116 | 117 | // SetWriteDeadline 118 | func (c *Conn) SetWriteDeadline(t time.Time) error { 119 | panic("TODO") 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import "net" 4 | 5 | // Interface assertions 6 | var _ net.Conn = (*Conn)(nil) 7 | -------------------------------------------------------------------------------- /dial.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func Dial(signaler Signaler) (net.Conn, error) { 8 | dc, raddr, err := signaler.Dial() 9 | if err != nil { 10 | return nil, err 11 | } 12 | 13 | // Ensure the channel is open 14 | ensureOpen(dc) 15 | 16 | conn, err := NewConn(dc, signaler.Addr(), raddr) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | return conn, nil 22 | } 23 | -------------------------------------------------------------------------------- /examples/http-signaler/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Usage 3 | Run the listening side: 4 | ```sh 5 | http-signaler 6 | ``` 7 | Run the dialing side: 8 | ```sh 9 | http-signaler -init 10 | ``` 11 | You should see them connect and exchange a message. -------------------------------------------------------------------------------- /examples/http-signaler/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/pions/dcnet" 9 | "github.com/pions/webrtc" 10 | ) 11 | 12 | func main() { 13 | init := flag.Bool("init", false, "initiate the connection") 14 | addr := flag.String("address", ":50000", "signaler address") 15 | flag.Parse() 16 | 17 | config := webrtc.RTCConfiguration{} 18 | signaler := NewHTTPSignaler(config, *addr) 19 | 20 | var conn net.Conn 21 | var err error 22 | if *init { 23 | conn, err = dcnet.Dial(signaler) 24 | check(err) 25 | } else { 26 | listener := dcnet.NewListener(signaler) 27 | conn, err = listener.Accept() 28 | check(err) 29 | } 30 | 31 | // Send a message to the other side 32 | _, err = conn.Write([]byte("Hello world!")) 33 | check(err) 34 | fmt.Println("Sent message") 35 | 36 | // Receive the message from the other side 37 | b := make([]byte, 100) 38 | _, err = conn.Read(b) 39 | check(err) 40 | fmt.Println("Received message:", string(b)) 41 | } 42 | 43 | func check(err error) { 44 | if err != nil { 45 | panic(err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/http-signaler/signaler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | "net" 8 | "net/http" 9 | 10 | "github.com/pions/dcnet" 11 | "github.com/pions/webrtc" 12 | ) 13 | 14 | func NewHTTPSignaler(config webrtc.RTCConfiguration, address string) *HTTPSignaler { 15 | return &HTTPSignaler{ 16 | config: webrtc.RTCConfiguration{}, 17 | address: address, 18 | } 19 | } 20 | 21 | type HTTPSignaler struct { 22 | config webrtc.RTCConfiguration 23 | address string 24 | } 25 | 26 | func (r *HTTPSignaler) Dial() (*webrtc.RTCDataChannel, net.Addr, error) { 27 | c, err := webrtc.New(r.config) 28 | if err != nil { 29 | return nil, nil, err 30 | } 31 | 32 | var dc *webrtc.RTCDataChannel 33 | 34 | dc, err = c.CreateDataChannel("data", nil) 35 | if err != nil { 36 | return nil, nil, err 37 | } 38 | 39 | // TODO: migrate to OnNegotiationNeeded when available 40 | offer, err := c.CreateOffer(nil) 41 | if err != nil { 42 | return nil, nil, err 43 | } 44 | 45 | b := new(bytes.Buffer) 46 | err = json.NewEncoder(b).Encode(offer) 47 | if err != nil { 48 | return nil, nil, err 49 | } 50 | 51 | resp, err := http.Post("http://"+r.address, "application/json; charset=utf-8", b) 52 | if err != nil { 53 | return nil, nil, err 54 | } 55 | defer resp.Body.Close() 56 | 57 | var answer webrtc.RTCSessionDescription 58 | err = json.NewDecoder(resp.Body).Decode(&answer) 59 | if err != nil { 60 | return nil, nil, err 61 | } 62 | 63 | if err := c.SetRemoteDescription(answer); err != nil { 64 | return nil, nil, err 65 | } 66 | return dc, &dcnet.NilAddr{}, nil 67 | } 68 | 69 | func (r *HTTPSignaler) Accept() (*webrtc.RTCDataChannel, net.Addr, error) { 70 | c, err := webrtc.New(r.config) 71 | if err != nil { 72 | return nil, nil, err 73 | } 74 | 75 | var dc *webrtc.RTCDataChannel 76 | 77 | res := make(chan *webrtc.RTCDataChannel) 78 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 79 | var offer webrtc.RTCSessionDescription 80 | err := json.NewDecoder(r.Body).Decode(&offer) 81 | if err != nil { 82 | log.Println("Failed to decode offer:", err) 83 | return 84 | } 85 | 86 | if err := c.SetRemoteDescription(offer); err != nil { 87 | log.Println("Failed to set remote description:", err) 88 | return 89 | } 90 | 91 | answer, err := c.CreateAnswer(nil) 92 | if err != nil { 93 | log.Println("Failed to create answer:", err) 94 | return 95 | } 96 | 97 | err = json.NewEncoder(w).Encode(answer) 98 | if err != nil { 99 | log.Println("Failed to encode answer:", err) 100 | return 101 | } 102 | 103 | c.OnDataChannel = func(d *webrtc.RTCDataChannel) { 104 | res <- d 105 | } 106 | 107 | }) 108 | 109 | go http.ListenAndServe(r.address, nil) 110 | 111 | dc = <-res 112 | return dc, &dcnet.NilAddr{}, nil 113 | } 114 | 115 | func (r *HTTPSignaler) Close() error { 116 | return nil 117 | } 118 | 119 | func (r *HTTPSignaler) Addr() net.Addr { 120 | return &dcnet.NilAddr{} 121 | } 122 | -------------------------------------------------------------------------------- /framer.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | // Framer allows sending and receiving packets with arbitrary over a io.ReadWriteCloser 10 | type Framer interface { 11 | io.ReadWriteCloser 12 | } 13 | 14 | // Framer allows receiving packets with arbitrary length over a io.ReadCloser 15 | type FrameReader interface { 16 | io.ReadCloser 17 | } 18 | 19 | // Framer allows sending packets with arbitrary length over a io.WriteCloser 20 | type FrameWriter interface { 21 | io.WriteCloser 22 | } 23 | 24 | // RTPFramer implements a RFC4571 Framer 25 | type RTPFramer struct { 26 | r *RTPFrameReader 27 | w *RTPFrameWriter 28 | } 29 | 30 | func NewRTPFramer(length int, c io.ReadWriteCloser) (*RTPFramer, error) { 31 | r, err := NewRTPFrameReader(c) 32 | if err != nil { 33 | return nil, err 34 | } 35 | w, err := NewRTPFrameWriter(length, c) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return &RTPFramer{ 40 | r: r, 41 | w: w, 42 | }, nil 43 | } 44 | 45 | func (f *RTPFramer) Read(p []byte) (n int, err error) { 46 | return f.r.Read(p) 47 | } 48 | 49 | func (f *RTPFramer) Write(p []byte) (n int, err error) { 50 | return f.w.Write(p) 51 | } 52 | 53 | func (f *RTPFramer) Close() error { 54 | return f.r.Close() 55 | } 56 | 57 | // RTPFrameReader implements a RFC4571 FrameReader 58 | // This framer only works over reliable, ordered connections 59 | type RTPFrameReader struct { 60 | length uint16 61 | inFrame bool 62 | curLength uint16 63 | c io.ReadCloser 64 | } 65 | 66 | // NewRTPFrameReader allows sending packets of up to 65535 bytes over the ReadWriteCloser 67 | func NewRTPFrameReader(c io.ReadCloser) (*RTPFrameReader, error) { 68 | return &RTPFrameReader{ 69 | c: c, 70 | }, nil 71 | } 72 | 73 | // Read reads a packet from the underlying stream. It returns EOF 74 | func (f *RTPFrameReader) Read(p []byte) (n int, err error) { 75 | n, err = f.c.Read(p) 76 | if err != nil { 77 | return n, err 78 | } 79 | 80 | if !f.inFrame { 81 | f.length = binary.BigEndian.Uint16(p[:2]) 82 | p = p[2:] 83 | n -= 2 84 | f.inFrame = true 85 | } 86 | 87 | newLen := int(f.curLength) + n 88 | if newLen > int(f.length) { 89 | return 0, errors.New("Receiving packet too long") 90 | } 91 | 92 | f.curLength = uint16(newLen) 93 | 94 | // Close out the packet 95 | if f.length == f.curLength { 96 | f.inFrame = false 97 | f.curLength = 0 98 | return n, io.EOF // TODO: Is this fine or should we return io.EOF forever and re-create the RTPFrameReader? 99 | } 100 | 101 | return n, nil 102 | } 103 | 104 | // Close closes the underlying ReadCloser 105 | func (f *RTPFrameReader) Close() error { 106 | return f.c.Close() 107 | } 108 | 109 | // RTPFrameWriter implements a RFC4571 FrameWriter 110 | // This framer only works over reliable, ordered connections 111 | type RTPFrameWriter struct { 112 | length uint16 113 | inFrame bool 114 | curLength uint16 115 | c io.WriteCloser 116 | } 117 | 118 | // NewRTPFrameWriter allows sending packets of up to 65535 bytes over the ReadWriteCloser 119 | func NewRTPFrameWriter(length int, c io.WriteCloser) (*RTPFrameWriter, error) { 120 | if length > 65535 { 121 | return nil, errors.New("Maximum length (65535) exeeded") 122 | } 123 | return &RTPFrameWriter{ 124 | length: uint16(length), 125 | c: c, 126 | }, nil 127 | } 128 | 129 | func (f *RTPFrameWriter) Write(p []byte) (n int, err error) { 130 | if int(f.curLength)+len(p) > int(f.length) { 131 | return 0, errors.New("Sending packet too long") 132 | } 133 | 134 | if !f.inFrame { 135 | b := make([]byte, 2) 136 | binary.BigEndian.PutUint16(b, f.length) 137 | _, err := f.c.Write(b) // Assumes we can at least write 2 bytes (or get an error) 138 | if err != nil { 139 | return 0, err 140 | } 141 | f.inFrame = true 142 | } 143 | 144 | if len(p) > 0 { 145 | n, err = f.c.Write(p) 146 | } 147 | 148 | f.curLength = f.curLength + uint16(n) 149 | 150 | // Close out the packet 151 | if f.length == f.curLength { 152 | f.curLength = 0 153 | f.inFrame = false 154 | } 155 | 156 | return n, err 157 | } 158 | 159 | // Close closes the underlying WriteCloser 160 | func (f *RTPFrameWriter) Close() error { 161 | return f.c.Close() 162 | } 163 | -------------------------------------------------------------------------------- /framer_test.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "math/rand" 7 | "strconv" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // Interface assertions 13 | var _ Framer = (*RTPFramer)(nil) 14 | var _ FrameReader = (*RTPFrameReader)(nil) 15 | var _ FrameWriter = (*RTPFrameWriter)(nil) 16 | 17 | func TestRTPFramer(t *testing.T) { 18 | 19 | testCases := []struct { 20 | sequence string 21 | }{ 22 | {""}, 23 | {"abc"}, 24 | {RandString(65535)}, 25 | } 26 | 27 | for i, testCase := range testCases { 28 | // TODO: make the pipe split the message 29 | pr, pw := io.Pipe() 30 | 31 | r, err := NewRTPFrameReader(pr) 32 | if err != nil { 33 | t.Fatalf("failed to create frame reader: %v", err) 34 | } 35 | defer r.Close() 36 | 37 | t.Run("NewRTPFramer_"+strconv.Itoa(i), func(t *testing.T) { 38 | 39 | w, err := NewRTPFrameWriter(len(testCase.sequence), pw) 40 | if err != nil { 41 | t.Fatalf("failed to create frame writer: %v", err) 42 | } 43 | defer w.Close() 44 | 45 | done := make(chan struct{}) 46 | 47 | // Avoid extreme waiting time on blocking bugs 48 | lim := time.AfterFunc(time.Second*2, func() { 49 | panic("timeout") 50 | }) 51 | defer lim.Stop() 52 | 53 | go func() { 54 | defer close(done) 55 | i, err := w.Write([]byte(testCase.sequence)) 56 | if i != len(testCase.sequence) { 57 | t.Fatalf("short write") 58 | } 59 | if err != nil { 60 | t.Fatalf("failed to write: %v", err) 61 | } 62 | }() 63 | 64 | result, err := ioutil.ReadAll(r) 65 | if err != nil { 66 | t.Fatalf("failed to read: %v", err) 67 | } 68 | 69 | <-done 70 | 71 | if string(result) != testCase.sequence { 72 | t.Errorf(string(result) + " != " + testCase.sequence) 73 | } 74 | 75 | }) 76 | } 77 | 78 | } 79 | 80 | func RandString(n int) string { 81 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 82 | b := make([]rune, n) 83 | for i := range b { 84 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 85 | } 86 | return string(b) 87 | } 88 | -------------------------------------------------------------------------------- /listener.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/pions/webrtc" 7 | ) 8 | 9 | // Listener creates an io.listener around an existing RTCPeerConnection 10 | // It is up to the supplier of the RTCPeerConnection to 11 | type Listener struct { 12 | s Signaler 13 | addr net.Addr 14 | } 15 | 16 | func NewListener(s Signaler) *Listener { 17 | res := &Listener{ 18 | s: s, 19 | } 20 | 21 | return res 22 | } 23 | 24 | func (l *Listener) Accept() (net.Conn, error) { 25 | dc, raddr, err := l.s.Accept() 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | // Ensure the channel is open 31 | ensureOpen(dc) 32 | 33 | conn, err := NewConn(dc, l.s.Addr(), raddr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return conn, nil 39 | } 40 | 41 | func ensureOpen(dc *webrtc.RTCDataChannel) { 42 | done := make(chan struct{}) 43 | open := func() { 44 | select { 45 | case <-done: 46 | default: 47 | close(done) 48 | } 49 | } 50 | dc.OnOpen = open 51 | 52 | if dc.ReadyState == webrtc.RTCDataChannelStateOpen { 53 | open() 54 | } 55 | <-done 56 | } 57 | 58 | func (l *Listener) Close() error { 59 | return l.s.Close() 60 | } 61 | 62 | func (l *Listener) Addr() net.Addr { 63 | return l.s.Addr() 64 | } 65 | -------------------------------------------------------------------------------- /listener_test.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import "net" 4 | 5 | // Interface assertions 6 | var _ net.Listener = (*Listener)(nil) 7 | -------------------------------------------------------------------------------- /signaler.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "net" 10 | 11 | "github.com/pions/webrtc" 12 | ) 13 | 14 | // Signaler 15 | type Signaler interface { 16 | Dial() (*webrtc.RTCDataChannel, net.Addr, error) 17 | Accept() (*webrtc.RTCDataChannel, net.Addr, error) 18 | Close() error 19 | Addr() net.Addr 20 | } 21 | 22 | // RWSignaler is a simple signaler over an io.ReadWriteCloser 23 | // It plainly exchanges the SDP offer/answer without peer address. 24 | type RWSignaler struct { 25 | c io.ReadWriteCloser 26 | config webrtc.RTCConfiguration // Maybe make this global? 27 | } 28 | 29 | // NewRWSignaler creates a new RWSignaler 30 | func NewRWSignaler(c io.ReadWriteCloser, rtcconfig webrtc.RTCConfiguration) *RWSignaler { 31 | s := &RWSignaler{ 32 | c: c, 33 | config: rtcconfig, 34 | } 35 | 36 | return s 37 | } 38 | 39 | // Accept creates WebRTC DataChannels by signaling over the ReadWriteCloser 40 | func (r *RWSignaler) Dial() (*webrtc.RTCDataChannel, net.Addr, error) { 41 | return r.doSignal(true) 42 | } 43 | 44 | // Accept creates WebRTC DataChannels by signaling over the ReadWriteCloser 45 | func (r *RWSignaler) Accept() (*webrtc.RTCDataChannel, net.Addr, error) { 46 | return r.doSignal(false) 47 | } 48 | 49 | func (r *RWSignaler) doSignal(init bool) (*webrtc.RTCDataChannel, net.Addr, error) { 50 | c, err := webrtc.New(r.config) 51 | if err != nil { 52 | return nil, nil, err 53 | } 54 | 55 | var dc *webrtc.RTCDataChannel 56 | var addr net.Addr 57 | 58 | if init { 59 | dc, err = c.CreateDataChannel("data", nil) 60 | if err != nil { 61 | return nil, nil, err 62 | } 63 | 64 | // TODO: migrate to OnNegotiationNeeded when available 65 | offer, err := c.CreateOffer(nil) 66 | if err != nil { 67 | return nil, nil, err 68 | } 69 | 70 | b, err := json.Marshal(offer) 71 | if err != nil { 72 | return nil, nil, err 73 | } 74 | 75 | fmt.Println("Sending initial offer", string(b)) 76 | fmt.Println() 77 | f, err := NewRTPFrameWriter(len(b), r.c) 78 | if err != nil { 79 | return nil, nil, err 80 | } 81 | 82 | // TODO: Don't assume we can write the entire offer at once 83 | _, err = f.Write(b) 84 | if err != nil { 85 | return nil, nil, err 86 | } 87 | addr = &NilAddr{} 88 | } 89 | 90 | go func() { 91 | for { 92 | f, err := NewRTPFrameReader(r.c) 93 | if err != nil { 94 | // TODO: Return error from Accept() 95 | log.Println(err) 96 | } 97 | b, err := ioutil.ReadAll(f) 98 | if err != nil { 99 | // TODO: Return error from Accept() 100 | log.Println(err) 101 | } 102 | 103 | fmt.Println() 104 | var desc webrtc.RTCSessionDescription 105 | err = json.Unmarshal(b, &desc) 106 | if err != nil { 107 | // TODO: Return error from Accept() 108 | log.Println(err) 109 | } 110 | 111 | if err := c.SetRemoteDescription(desc); err != nil { 112 | panic(err) 113 | } 114 | fmt.Println("Got", desc.Type, string(b)) 115 | log.Println(desc.Type, webrtc.RTCSdpTypeOffer, desc.Type == webrtc.RTCSdpTypeOffer) 116 | if desc.Type == webrtc.RTCSdpTypeOffer { 117 | // Sets the LocalDescription, and starts our UDP listeners 118 | answer, err := c.CreateAnswer(nil) 119 | if err != nil { 120 | panic(err) 121 | } 122 | 123 | b, err := json.Marshal(answer) 124 | if err != nil { 125 | // TODO: Return error from Accept() 126 | log.Println(err) 127 | } 128 | 129 | fmt.Println("Sending answer", string(b)) 130 | fmt.Println() 131 | f, err := NewRTPFrameWriter(len(b), r.c) 132 | if err != nil { 133 | // TODO: Return error from Accept() 134 | log.Println(err) 135 | } 136 | 137 | // TODO: Don't assume we can write the entire answer at once 138 | _, err = f.Write(b) 139 | if err != nil { 140 | // TODO: Return error from Accept() 141 | log.Println(err) 142 | } 143 | } 144 | } 145 | }() 146 | 147 | if dc == nil { 148 | res := make(chan struct { 149 | d *webrtc.RTCDataChannel 150 | a net.Addr 151 | }) 152 | 153 | c.OnDataChannel = func(d *webrtc.RTCDataChannel) { 154 | fmt.Printf("New DataChannel %s %d\n", d.Label, d.ID) 155 | res <- struct { 156 | d *webrtc.RTCDataChannel 157 | a net.Addr 158 | }{ 159 | d: d, 160 | a: &NilAddr{}, 161 | } 162 | } 163 | 164 | e := <-res 165 | dc = e.d 166 | addr = e.a 167 | } 168 | 169 | return dc, addr, nil 170 | } 171 | 172 | // Close closed the ReadWriteCloser 173 | func (r *RWSignaler) Close() error { 174 | return r.c.Close() 175 | } 176 | 177 | func (r *RWSignaler) Addr() net.Addr { 178 | return &NilAddr{} 179 | } 180 | 181 | // MultiSignaler combines the power of many signalers 182 | type MultiSignaler struct { 183 | s []Signaler 184 | } 185 | 186 | // NewMultiSignaler creates a new MultiSignaler 187 | func NewMultiSignaler(set ...Signaler) (*MultiSignaler, error) { 188 | res := &MultiSignaler{ 189 | s: set, 190 | } 191 | return res, nil 192 | } 193 | 194 | func (s *MultiSignaler) Accept() (*webrtc.RTCDataChannel, net.Addr, error) { 195 | // TODO: accept on all signalers 196 | panic("TODO") 197 | return nil, nil, nil 198 | } 199 | 200 | func (s *MultiSignaler) Close() error { 201 | var closeErr error 202 | for _, signaler := range s.s { 203 | err := signaler.Close() 204 | if err != nil { 205 | closeErr = err 206 | } 207 | } 208 | return closeErr 209 | } 210 | 211 | func (s *MultiSignaler) Addr() net.Addr { 212 | // We assume all signalers use the same addr 213 | return s.s[0].Addr() 214 | } 215 | -------------------------------------------------------------------------------- /signaler_test.go: -------------------------------------------------------------------------------- 1 | package dcnet 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/pions/webrtc" 10 | ) 11 | 12 | // Interface assertions 13 | var _ Signaler = (*RWSignaler)(nil) 14 | var _ Signaler = (*MultiSignaler)(nil) 15 | 16 | func TestRWSignaler(t *testing.T) { 17 | 18 | // Avoid extreme waiting time on blocking bugs 19 | lim := time.AfterFunc(time.Second*5, func() { 20 | panic("timeout") 21 | }) 22 | defer lim.Stop() 23 | 24 | connA, connB := net.Pipe() 25 | 26 | sigA := NewRWSignaler(connA, webrtc.RTCConfiguration{}, true) 27 | sigB := NewRWSignaler(connB, webrtc.RTCConfiguration{}, false) 28 | 29 | doneA := validateAccept(t, "A", sigA) 30 | doneB := validateAccept(t, "B", sigB) 31 | 32 | <-doneA 33 | <-doneB 34 | } 35 | 36 | func validateAccept(t *testing.T, name string, sig Signaler) chan struct{} { 37 | done := make(chan struct{}) 38 | 39 | go func() { 40 | defer close(done) 41 | _, _, err := sig.Accept() 42 | if err != nil { 43 | t.Errorf("failed to accept on %s: %v", name, err) 44 | return 45 | } 46 | log.Println("Accept on", name) 47 | 48 | }() 49 | 50 | return done 51 | } 52 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/pions/dcnet" 10 | "github.com/pions/webrtc" 11 | ) 12 | 13 | func main() { 14 | 15 | // Avoid extreme waiting time on blocking bugs 16 | lim := time.AfterFunc(time.Second*10, func() { 17 | panic("timeout") 18 | }) 19 | defer lim.Stop() 20 | 21 | connA, connB := net.Pipe() 22 | 23 | sigA := dcnet.NewRWSignaler(connA, webrtc.RTCConfiguration{}, true) 24 | sigB := dcnet.NewRWSignaler(connB, webrtc.RTCConfiguration{}, false) 25 | 26 | doneA := validateAccept("A", sigA) 27 | doneB := validateAccept("B", sigB) 28 | 29 | <-doneA 30 | <-doneB 31 | 32 | log.Println("All good!") 33 | 34 | } 35 | 36 | func validateAccept(name string, sig dcnet.Signaler) chan struct{} { 37 | done := make(chan struct{}) 38 | 39 | go func() { 40 | defer close(done) 41 | _, _, err := sig.Accept() 42 | if err != nil { 43 | panic(fmt.Sprintf("failed to accept on %s: %v", name, err)) 44 | return 45 | } 46 | log.Println("Accept on", name) 47 | 48 | // TODO: test sending something once pions to pions works 49 | }() 50 | 51 | return done 52 | } 53 | --------------------------------------------------------------------------------