mirror of
https://github.com/pion/webrtc.git
synced 2025-10-06 15:46:54 +08:00
datachannel: send OpenChannel message
This commit is contained in:
31
examples/data-channels-create/README.md
Normal file
31
examples/data-channels-create/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# data-channels
|
||||||
|
data-channels is a pion-WebRTC application that shows how you can send/recv DataChannel messages from a web browser
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
### Download data-channels
|
||||||
|
```
|
||||||
|
go get github.com/pions/webrtc/examples/data-channels
|
||||||
|
```
|
||||||
|
|
||||||
|
### Open data-channels example page
|
||||||
|
[jsfiddle.net](https://jsfiddle.net/gh/get/library/pure/pions/webrtc/tree/master/examples/data-channels/jsfiddle)
|
||||||
|
|
||||||
|
### Run data-channels, with your browsers SessionDescription as stdin
|
||||||
|
In the jsfiddle the top textarea is your browser's session description, copy that and:
|
||||||
|
#### Linux/macOS
|
||||||
|
Run `echo $BROWSER_SDP | data-channels`
|
||||||
|
#### Windows
|
||||||
|
1. Paste the SessionDescription into a file.
|
||||||
|
1. Run `data-channels < my_file`
|
||||||
|
|
||||||
|
### Input data-channels's SessionDescription into your browser
|
||||||
|
Copy the text that `data-channels` just emitted and copy into second text area
|
||||||
|
|
||||||
|
### Hit 'Start Session' in jsfiddle
|
||||||
|
Under Start Session you should see 'Checking' as it starts connecting. If everything worked you should see `New DataChannel foo 1`
|
||||||
|
|
||||||
|
Now you can put whatever you want in the `Message` textarea, and when you hit `Send Message` it should appear in your browser!
|
||||||
|
|
||||||
|
You can also type in your terminal, and when you hit enter it will appear in your web browser.
|
||||||
|
|
||||||
|
Congrats, you have used pion-WebRTC! Now start building something cool
|
1
examples/data-channels-create/jsfiddle/demo.css
Normal file
1
examples/data-channels-create/jsfiddle/demo.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
5
examples/data-channels-create/jsfiddle/demo.details
Normal file
5
examples/data-channels-create/jsfiddle/demo.details
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
name: data-channels
|
||||||
|
description: Example of using pion-WebRTC to communicate with a web browser using bi-direction DataChannels
|
||||||
|
authors:
|
||||||
|
- Sean DuBois
|
9
examples/data-channels-create/jsfiddle/demo.html
Normal file
9
examples/data-channels-create/jsfiddle/demo.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Browser base64 Session Description <textarea id="localSessionDescription" readonly="true"></textarea> <br />
|
||||||
|
Golang base64 Session Description: <textarea id="remoteSessionDescription"></textarea> <br/>
|
||||||
|
<button onclick="window.startSession()"> Start Session </button> <br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
Message: <textarea id="message">This is my DataChannel message!</textarea> <br/>
|
||||||
|
<button onclick="window.sendMessage()"> Send Message </button> <br />
|
||||||
|
|
||||||
|
<div id="logs"></div>
|
42
examples/data-channels-create/jsfiddle/demo.js
Normal file
42
examples/data-channels-create/jsfiddle/demo.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/* eslint-env browser */
|
||||||
|
|
||||||
|
let pc = new RTCPeerConnection()
|
||||||
|
let log = msg => {
|
||||||
|
document.getElementById('logs').innerHTML += msg + '<br>'
|
||||||
|
}
|
||||||
|
|
||||||
|
let sendChannel = pc.createDataChannel()
|
||||||
|
console.log(sendChannel.id)
|
||||||
|
sendChannel.onclose = () => console.log('sendChannel has closed')
|
||||||
|
sendChannel.onopen = () => console.log('sendChannel has opened')
|
||||||
|
sendChannel.onmessage = e => log(`sendChannel got '${e.data}'`)
|
||||||
|
|
||||||
|
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
|
||||||
|
|
||||||
|
pc.onnegotiationneeded = e =>
|
||||||
|
pc.createOffer({ }).then(d => {
|
||||||
|
document.getElementById('localSessionDescription').value = btoa(d.sdp)
|
||||||
|
return pc.setLocalDescription(d)
|
||||||
|
}).catch(log)
|
||||||
|
|
||||||
|
window.sendMessage = () => {
|
||||||
|
let message = document.getElementById('message').value
|
||||||
|
if (message === '') {
|
||||||
|
return alert('Message must not be empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
sendChannel.send(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.startSession = () => {
|
||||||
|
let sd = document.getElementById('remoteSessionDescription').value
|
||||||
|
if (sd === '') {
|
||||||
|
return alert('Session Description must not be empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pc.setRemoteDescription(new RTCSessionDescription({type: 'answer', sdp: atob(sd)}))
|
||||||
|
} catch (e) {
|
||||||
|
alert(e)
|
||||||
|
}
|
||||||
|
}
|
110
examples/data-channels-create/main.go
Normal file
110
examples/data-channels-create/main.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pions/webrtc"
|
||||||
|
"github.com/pions/webrtc/pkg/datachannel"
|
||||||
|
"github.com/pions/webrtc/pkg/ice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func randSeq(n int) string {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letters[r.Intn(len(letters))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
rawSd, err := reader.ReadString('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("")
|
||||||
|
sd, err := base64.StdEncoding.DecodeString(rawSd)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Everything below is the pion-WebRTC API, thanks for using it! */
|
||||||
|
|
||||||
|
// Create a new RTCPeerConnection
|
||||||
|
peerConnection, err := webrtc.New(webrtc.RTCConfiguration{
|
||||||
|
ICEServers: []webrtc.RTCICEServer{
|
||||||
|
{
|
||||||
|
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the handler for ICE connection state
|
||||||
|
// This will notify you when the peer has connected/disconnected
|
||||||
|
peerConnection.OnICEConnectionStateChange = func(connectionState ice.ConnectionState) {
|
||||||
|
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We have to send the offer otherwise it's empty.
|
||||||
|
|
||||||
|
d, err := peerConnection.CreateDataChannel("data", nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("New DataChannel %s %d\n", d.Label, d.ID)
|
||||||
|
|
||||||
|
d.Lock()
|
||||||
|
d.Onmessage = func(payload datachannel.Payload) {
|
||||||
|
switch p := payload.(type) {
|
||||||
|
case *datachannel.PayloadString:
|
||||||
|
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), d.Label, string(p.Data))
|
||||||
|
case *datachannel.PayloadBinary:
|
||||||
|
fmt.Printf("Message '%s' from DataChannel '%s' payload '% 02x'\n", p.PayloadType().String(), d.Label, p.Data)
|
||||||
|
default:
|
||||||
|
fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), d.Label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.Unlock()
|
||||||
|
|
||||||
|
// Set the remote SessionDescription
|
||||||
|
offer := webrtc.RTCSessionDescription{
|
||||||
|
Type: webrtc.RTCSdpTypeOffer,
|
||||||
|
Sdp: string(sd),
|
||||||
|
}
|
||||||
|
if err := peerConnection.SetRemoteDescription(offer); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the LocalDescription, and starts our UDP listeners
|
||||||
|
answer, err := peerConnection.CreateAnswer(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the LocalDescription and take it to base64 so we can paste in browser
|
||||||
|
fmt.Println(base64.StdEncoding.EncodeToString([]byte(answer.Sdp)))
|
||||||
|
fmt.Println("Random messages will now be sent to any connected DataChannels every 5 seconds")
|
||||||
|
for {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
message := randSeq(15)
|
||||||
|
fmt.Printf("Sending %s \n", message)
|
||||||
|
|
||||||
|
err := d.Send(datachannel.PayloadString{Data: []byte(message)})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -27,7 +28,7 @@ func randSeq(n int) string {
|
|||||||
func main() {
|
func main() {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
rawSd, err := reader.ReadString('\n')
|
rawSd, err := reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil && err != io.EOF {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -270,3 +270,23 @@ func (m *Manager) iceOutboundHandler(raw []byte, local *stun.TransportAddr, remo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SendOpenChannelMessage(streamIdentifier uint16, label string) error {
|
||||||
|
msg := &datachannel.ChannelOpen{
|
||||||
|
ChannelType: datachannel.ChannelTypeReliable,
|
||||||
|
Priority: datachannel.PriorityNormal,
|
||||||
|
ReliabilityParameter: 0,
|
||||||
|
|
||||||
|
Label: []byte(label),
|
||||||
|
Protocol: []byte(""),
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMsg, err := msg.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error Marshaling ChannelOpen %v", err)
|
||||||
|
}
|
||||||
|
if err = m.sctpAssociation.HandleOutbound(rawMsg, streamIdentifier, sctp.PayloadTypeWebRTCDCEP); err != nil {
|
||||||
|
return fmt.Errorf("Error sending ChannelOpen %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -28,7 +28,7 @@ ChannelOpen represents a DATA_CHANNEL_OPEN Message
|
|||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
*/
|
*/
|
||||||
type ChannelOpen struct {
|
type ChannelOpen struct {
|
||||||
ChannelType byte
|
ChannelType ChannelType
|
||||||
Priority uint16
|
Priority uint16
|
||||||
ReliabilityParameter uint32
|
ReliabilityParameter uint32
|
||||||
|
|
||||||
@@ -40,6 +40,43 @@ const (
|
|||||||
channelOpenHeaderLength = 12
|
channelOpenHeaderLength = 12
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ChannelType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ChannelTypeReliable determines the Data Channel provides a
|
||||||
|
// reliable in-order bi-directional communication.
|
||||||
|
ChannelTypeReliable = 0x00
|
||||||
|
// ChannelTypeReliableUnordered determines the Data Channel
|
||||||
|
// provides a reliable unordered bi-directional communication.
|
||||||
|
ChannelTypeReliableUnordered = 0x80
|
||||||
|
// ChannelTypePartialReliableRexmit determines the Data Channel
|
||||||
|
// provides a partially-reliable in-order bi-directional communication.
|
||||||
|
// User messages will not be retransmitted more times than specified in the Reliability Parameter.
|
||||||
|
ChannelTypePartialReliableRexmit = 0x01
|
||||||
|
// ChannelTypePartialReliableRexmitUnordered determines
|
||||||
|
// the Data Channel provides a partial reliable unordered bi-directional communication.
|
||||||
|
// User messages will not be retransmitted more times than specified in the Reliability Parameter.
|
||||||
|
ChannelTypePartialReliableRexmitUnordered = 0x81
|
||||||
|
// ChannelTypePartialReliableTimed determines the Data Channel
|
||||||
|
// provides a partial reliable in-order bi-directional communication.
|
||||||
|
// User messages might not be transmitted or retransmitted after
|
||||||
|
// a specified life-time given in milli- seconds in the Reliability Parameter.
|
||||||
|
// This life-time starts when providing the user message to the protocol stack.
|
||||||
|
ChannelTypePartialReliableTimed = 0x02
|
||||||
|
// The Data Channel provides a partial reliable unordered bi-directional
|
||||||
|
// communication. User messages might not be transmitted or retransmitted
|
||||||
|
// after a specified life-time given in milli- seconds in the Reliability Parameter.
|
||||||
|
// This life-time starts when providing the user message to the protocol stack.
|
||||||
|
ChannelTypePartialReliableTimedUnordered = 0x82
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PriorityBelowNormal uint16 = 128
|
||||||
|
PriorityNormal uint16 = 256
|
||||||
|
PriorityHigh uint16 = 512
|
||||||
|
PriorityExtraHigh uint16 = 1024
|
||||||
|
)
|
||||||
|
|
||||||
// Marshal returns raw bytes for the given message
|
// Marshal returns raw bytes for the given message
|
||||||
func (c *ChannelOpen) Marshal() ([]byte, error) {
|
func (c *ChannelOpen) Marshal() ([]byte, error) {
|
||||||
labelLength := len(c.Label)
|
labelLength := len(c.Label)
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func TestChannelOpenMarshal(t *testing.T) {
|
func TestChannelOpenMarshal(t *testing.T) {
|
||||||
msg := ChannelOpen{
|
msg := ChannelOpen{
|
||||||
ChannelType: 0,
|
ChannelType: ChannelTypeReliable,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
ReliabilityParameter: 0,
|
ReliabilityParameter: 0,
|
||||||
|
|
||||||
@@ -18,13 +18,15 @@ func TestChannelOpenMarshal(t *testing.T) {
|
|||||||
|
|
||||||
rawMsg, err := msg.Marshal()
|
rawMsg, err := msg.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to marshal: %v", err)
|
t.Errorf("Failed to marshal: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
result := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72}
|
result := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72}
|
||||||
|
|
||||||
if len(rawMsg) != len(result) {
|
if len(rawMsg) != len(result) {
|
||||||
t.Fatalf("%q != %q", rawMsg, result)
|
t.Errorf("%q != %q", rawMsg, result)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, v := range rawMsg {
|
for i, v := range rawMsg {
|
||||||
@@ -39,12 +41,14 @@ func TestChannelAckMarshal(t *testing.T) {
|
|||||||
msg := ChannelAck{}
|
msg := ChannelAck{}
|
||||||
rawMsg, err := msg.Marshal()
|
rawMsg, err := msg.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to marshal: %v", err)
|
t.Errorf("Failed to marshal: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
result := []byte{0x02, 0x00, 0x00, 0x00}
|
result := []byte{0x02, 0x00, 0x00, 0x00}
|
||||||
|
|
||||||
if len(rawMsg) != len(result) {
|
if len(rawMsg) != len(result) {
|
||||||
t.Fatalf("%q != %q", rawMsg, result)
|
t.Errorf("%q != %q", rawMsg, result)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, v := range rawMsg {
|
for i, v := range rawMsg {
|
||||||
@@ -66,7 +70,7 @@ func TestChannelOpenUnmarshal(t *testing.T) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(errors.Wrap(err, "Unmarshal failed, ChannelOpen"))
|
t.Error(errors.Wrap(err, "Unmarshal failed, ChannelOpen"))
|
||||||
} else if msg.ChannelType != 0 {
|
} else if msg.ChannelType != ChannelTypeReliable {
|
||||||
t.Error(errors.Errorf("ChannelType should be 0"))
|
t.Error(errors.Errorf("ChannelType should be 0"))
|
||||||
} else if msg.Priority != 0 {
|
} else if msg.Priority != 0 {
|
||||||
t.Error(errors.Errorf("Priority should be 0"))
|
t.Error(errors.Errorf("Priority should be 0"))
|
||||||
@@ -83,7 +87,8 @@ func TestChannelAckUnmarshal(t *testing.T) {
|
|||||||
rawMsg := []byte{0x02}
|
rawMsg := []byte{0x02}
|
||||||
msgUncast, err := Parse(rawMsg)
|
msgUncast, err := Parse(rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to parse: %v", err)
|
t.Errorf("Failed to parse: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := msgUncast.(*ChannelAck)
|
_, ok := msgUncast.(*ChannelAck)
|
||||||
|
@@ -51,13 +51,14 @@ func (p RTCPriorityType) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RTCDataChannelInit can be used to configure properties of the underlying channel such as data reliability.
|
||||||
type RTCDataChannelInit struct {
|
type RTCDataChannelInit struct {
|
||||||
Ordered bool
|
Ordered bool
|
||||||
MaxPacketLifeTime *uint16
|
MaxPacketLifeTime *uint16
|
||||||
MaxRetransmits *uint16
|
MaxRetransmits *uint16
|
||||||
Protocol string
|
Protocol string
|
||||||
Negotiated bool
|
Negotiated bool
|
||||||
Id uint16
|
ID uint16
|
||||||
Priority RTCPriorityType
|
Priority RTCPriorityType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,11 +83,15 @@ func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChan
|
|||||||
negotiated = options.Negotiated
|
negotiated = options.Negotiated
|
||||||
}
|
}
|
||||||
|
|
||||||
var id uint16 = 0
|
var id uint16
|
||||||
if negotiated {
|
if negotiated {
|
||||||
id = options.Id
|
id = options.ID
|
||||||
} else {
|
} else {
|
||||||
// TODO: generate id
|
var err error
|
||||||
|
id, err = r.generateDataChannelID(true) // TODO: base on DTLS role
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if id > 65534 {
|
if id > 65534 {
|
||||||
@@ -98,20 +103,38 @@ func (r *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChan
|
|||||||
return nil, &OperationError{Err: ErrMaxDataChannels}
|
return nil, &OperationError{Err: ErrMaxDataChannels}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Actually allocate datachannel
|
_ = ordered // TODO
|
||||||
|
_ = priority // TODO
|
||||||
res := &RTCDataChannel{
|
res := &RTCDataChannel{
|
||||||
Label: label,
|
Label: label,
|
||||||
ID: id,
|
ID: id,
|
||||||
rtcPeerConnection: r,
|
rtcPeerConnection: r,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle settings:
|
// Remember datachannel
|
||||||
_ = ordered
|
r.dataChannels[id] = res
|
||||||
_ = priority
|
|
||||||
|
// Send opening message
|
||||||
|
r.networkManager.SendOpenChannelMessage(id, label)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RTCPeerConnection) generateDataChannelID(client bool) (uint16, error) {
|
||||||
|
var id uint16
|
||||||
|
if !client {
|
||||||
|
id++
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; id < r.sctp.MaxChannels-1; id += 2 {
|
||||||
|
_, ok := r.dataChannels[id]
|
||||||
|
if !ok {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, &OperationError{Err: ErrMaxDataChannels}
|
||||||
|
}
|
||||||
|
|
||||||
// Send sends the passed message to the DataChannel peer
|
// Send sends the passed message to the DataChannel peer
|
||||||
func (r *RTCDataChannel) Send(p datachannel.Payload) error {
|
func (r *RTCDataChannel) Send(p datachannel.Payload) error {
|
||||||
if err := r.rtcPeerConnection.networkManager.SendDataChannelMessage(p, r.ID); err != nil {
|
if err := r.rtcPeerConnection.networkManager.SendDataChannelMessage(p, r.ID); err != nil {
|
||||||
|
35
rtcdatachannel_test.go
Normal file
35
rtcdatachannel_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package webrtc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateDataChannelID(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
client bool
|
||||||
|
c *RTCPeerConnection
|
||||||
|
result uint16
|
||||||
|
}{
|
||||||
|
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 0},
|
||||||
|
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 0},
|
||||||
|
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 2},
|
||||||
|
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}}, 4},
|
||||||
|
{true, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}}, 2},
|
||||||
|
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 1},
|
||||||
|
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 1},
|
||||||
|
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 3},
|
||||||
|
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}}, 5},
|
||||||
|
{false, &RTCPeerConnection{sctp: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}}, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
id, err := testCase.c.generateDataChannelID(testCase.client)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to generate id: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if id != testCase.result {
|
||||||
|
t.Errorf("Wrong id: %d expected %d", id, testCase.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user