├── 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 |
--------------------------------------------------------------------------------