Repository: pion/webrtc
Pull request #1417 (2020-09-12 07:47:13)

Move candidate parsing to pion/ice
Instead of having logic in pion/webrtc and pion/sdp move all logic to
pion/ice. Users of pion/ice don't want to import these packages to use
basic pion/ice functionality.

Sean-Der: @korymiller1489 `pion/ice` is ready for you to use!
codecov[bot]: # [Codecov](https://codecov.io/gh/pion/webrtc/pull/1417?src=pr&el=h1) Report
> Merging [#1417](https://codecov.io/gh/pion/webrtc/pull/1417?src=pr&el=desc) into [master](https://codecov.io/gh/pion/webrtc/commit/81cfadaf06cbe17724b1c91c28dbaf0eb005abb6?el=desc) will **increase** coverage by `1.33%`.
> The diff coverage is `68.75%`.

[![Impacted file tree graph](https://codecov.io/gh/pion/webrtc/pull/1417/graphs/tree.svg?width=650&height=150&src=pr&token=XOOuQo07I2)](https://codecov.io/gh/pion/webrtc/pull/1417?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1417      +/-   ##
==========================================
+ Coverage   75.74%   77.08%   +1.33%     
==========================================
  Files          73       65       -8     
  Lines        5962     5289     -673     
==========================================
- Hits         4516     4077     -439     
+ Misses       1096      897     -199     
+ Partials      350      315      -35     
```

| Flag | Coverage Δ | |
|---|---|---|
| #go | `77.08% <68.75%> (-0.33%)` | :arrow_down: |
| #wasm | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/pion/webrtc/pull/1417?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [dtlsrole.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-ZHRsc3JvbGUuZ28=) | `100.00% <ø> (ø)` | |
| [mediaengine.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-bWVkaWFlbmdpbmUuZ28=) | `73.42% <ø> (ø)` | |
| [rtptransceiver.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-cnRwdHJhbnNjZWl2ZXIuZ28=) | `68.23% <ø> (ø)` | |
| [settingengine.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-c2V0dGluZ2VuZ2luZS5nbw==) | `63.15% <ø> (ø)` | |
| [sdp.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-c2RwLmdv) | `80.54% <60.00%> (-2.38%)` | :arrow_down: |
| [icecandidate.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-aWNlY2FuZGlkYXRlLmdv) | `73.26% <100.00%> (-4.59%)` | :arrow_down: |
| [peerconnection.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-cGVlcmNvbm5lY3Rpb24uZ28=) | `76.57% <100.00%> (-0.35%)` | :arrow_down: |
| [rtpreceiver.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-cnRwcmVjZWl2ZXIuZ28=) | `53.38% <0.00%> (-3.01%)` | :arrow_down: |
| [js\_utils.go](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree#diff-anNfdXRpbHMuZ28=) | | |
| ... and [8 more](https://codecov.io/gh/pion/webrtc/pull/1417/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/webrtc/pull/1417?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/webrtc/pull/1417?src=pr&el=footer). Last update [81cfada...1bc6b9e](https://codecov.io/gh/pion/webrtc/pull/1417?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/dtlsrole.go b/dtlsrole.go
index f1cbfa88..6e67f60e 100644
--- a/dtlsrole.go
+++ b/dtlsrole.go
@@ -1,7 +1,7 @@
 package webrtc
 
 import (
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 )
 
 // DTLSRole indicates the role of the DTLS transport.
diff --git a/examples/simulcast/main.go b/examples/simulcast/main.go
index e15f809a..7a253fb0 100644
--- a/examples/simulcast/main.go
+++ b/examples/simulcast/main.go
@@ -8,7 +8,7 @@ import (
 	"time"
 
 	"github.com/pion/rtcp"
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 	"github.com/pion/webrtc/v3"
 	"github.com/pion/webrtc/v3/examples/internal/signal"
 )
diff --git a/go.mod b/go.mod
index c97ff358..22c6fe73 100644
--- a/go.mod
+++ b/go.mod
@@ -5,14 +5,14 @@ go 1.12
 require (
 	github.com/pion/datachannel v1.4.20
 	github.com/pion/dtls/v2 v2.0.2
-	github.com/pion/ice/v2 v2.0.1
+	github.com/pion/ice/v2 v2.0.6
 	github.com/pion/logging v0.2.2
 	github.com/pion/quic v0.1.4
 	github.com/pion/randutil v0.1.0
 	github.com/pion/rtcp v1.2.3
 	github.com/pion/rtp v1.6.0
 	github.com/pion/sctp v1.7.9
-	github.com/pion/sdp/v2 v2.4.0
+	github.com/pion/sdp/v3 v3.0.0
 	github.com/pion/srtp v1.5.1
 	github.com/pion/transport v0.10.1
 	github.com/sclevine/agouti v3.0.0+incompatible
diff --git a/go.sum b/go.sum
index 4379181e..a2897573 100644
--- a/go.sum
+++ b/go.sum
@@ -57,8 +57,8 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
 github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -104,8 +104,8 @@ github.com/pion/datachannel v1.4.20 h1:+uYUrxbhGuEt+9En81Necda5ul8M2h7mMsvGWkYZ/
 github.com/pion/datachannel v1.4.20/go.mod h1:hsjWYdTW5fMmtM4hVIxUNYqViRPv2A6ixzkQFd82wSc=
 github.com/pion/dtls/v2 v2.0.2 h1:FHCHTiM182Y8e15aFTiORroiATUI16ryHiQh8AIOJ1E=
 github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I=
-github.com/pion/ice/v2 v2.0.1 h1:uiWR6YOm6SaFAGET/fEdDsb+P67UD05Xig0sx0rP268=
-github.com/pion/ice/v2 v2.0.1/go.mod h1:e9mMZiQdz7f4uSp8umiOMa7JSAfmmtef3g4g29BkY5E=
+github.com/pion/ice/v2 v2.0.6 h1:7Jf3AX6VIjgO2tGRyT0RGGxkDYOF4m5I5DQzf34IN1Y=
+github.com/pion/ice/v2 v2.0.6/go.mod h1:xOXvVRlQC/B7FPJeJYKY6IepFRAKb3t1un1K9boYaaQ=
 github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
 github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
 github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
@@ -121,8 +121,8 @@ github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
 github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
 github.com/pion/sctp v1.7.9 h1:n+A37cTMU08xL3Oodkz39XjtPReQliKyk01q96mGB5M=
 github.com/pion/sctp v1.7.9/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
-github.com/pion/sdp/v2 v2.4.0 h1:luUtaETR5x2KNNpvEMv/r4Y+/kzImzbz4Lm1z8eQNQI=
-github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
+github.com/pion/sdp/v3 v3.0.0 h1:is7eDxB7R8CTmxh9Q4z1YaKQxDCPGOCMRbaKfj/P6yU=
+github.com/pion/sdp/v3 v3.0.0/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
 github.com/pion/srtp v1.5.1 h1:9Q3jAfslYZBt+C69SI/ZcONJh9049JUHZWYRRf5KEKw=
 github.com/pion/srtp v1.5.1/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA=
 github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
@@ -214,6 +214,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
diff --git a/icecandidate.go b/icecandidate.go
index 2bbbcf86..8d21b210 100644
--- a/icecandidate.go
+++ b/icecandidate.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 
 	"github.com/pion/ice/v2"
-	"github.com/pion/sdp/v2"
 )
 
 // ICECandidate represents a ice candidate
@@ -50,7 +49,7 @@ func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) {
 
 	c := ICECandidate{
 		statsID:    i.ID(),
-		Foundation: "foundation",
+		Foundation: i.Foundation(),
 		Priority:   i.Priority(),
 		Address:    i.Address(),
 		Protocol:   protocol,
@@ -79,6 +78,8 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
 			Port:        int(c.Port),
 			Component:   c.Component,
 			TCPType:     ice.NewTCPType(c.TCPType),
+			Foundation:  c.Foundation,
+			Priority:    c.Priority,
 		}
 		return ice.NewCandidateHost(&config)
 	case ICECandidateTypeSrflx:
@@ -88,6 +89,8 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
 			Address:     c.Address,
 			Port:        int(c.Port),
 			Component:   c.Component,
+			Foundation:  c.Foundation,
+			Priority:    c.Priority,
 			RelAddr:     c.RelatedAddress,
 			RelPort:     int(c.RelatedPort),
 		}
@@ -99,6 +102,8 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
 			Address:     c.Address,
 			Port:        int(c.Port),
 			Component:   c.Component,
+			Foundation:  c.Foundation,
+			Priority:    c.Priority,
 			RelAddr:     c.RelatedAddress,
 			RelPort:     int(c.RelatedPort),
 		}
@@ -110,6 +115,8 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
 			Address:     c.Address,
 			Port:        int(c.Port),
 			Component:   c.Component,
+			Foundation:  c.Foundation,
+			Priority:    c.Priority,
 			RelAddr:     c.RelatedAddress,
 			RelPort:     int(c.RelatedPort),
 		}
@@ -142,72 +149,20 @@ func (c ICECandidate) String() string {
 	return ic.String()
 }
 
-func iceCandidateToSDP(c ICECandidate) sdp.ICECandidate {
-	var extensions []sdp.ICECandidateAttribute
-
-	if c.Protocol == ICEProtocolTCP && c.TCPType != "" {
-		extensions = append(extensions, sdp.ICECandidateAttribute{
-			Key:   "tcptype",
-			Value: c.TCPType,
-		})
-	}
-
-	return sdp.ICECandidate{
-		Foundation:          c.Foundation,
-		Priority:            c.Priority,
-		Address:             c.Address,
-		Protocol:            c.Protocol.String(),
-		Port:                c.Port,
-		Component:           c.Component,
-		Typ:                 c.Typ.String(),
-		RelatedAddress:      c.RelatedAddress,
-		RelatedPort:         c.RelatedPort,
-		ExtensionAttributes: extensions,
-	}
-}
-
-func newICECandidateFromSDP(c sdp.ICECandidate) (ICECandidate, error) {
-	typ, err := NewICECandidateType(c.Typ)
-	if err != nil {
-		return ICECandidate{}, err
-	}
-	protocol, err := NewICEProtocol(c.Protocol)
-	if err != nil {
-		return ICECandidate{}, err
-	}
-
-	var tcpType string
-	if protocol == ICEProtocolTCP {
-		for _, attr := range c.ExtensionAttributes {
-			if attr.Key == "tcptype" {
-				tcpType = attr.Value
-				break
-			}
-		}
-	}
-
-	return ICECandidate{
-		Foundation:     c.Foundation,
-		Priority:       c.Priority,
-		Address:        c.Address,
-		Protocol:       protocol,
-		Port:           c.Port,
-		Component:      c.Component,
-		Typ:            typ,
-		RelatedAddress: c.RelatedAddress,
-		RelatedPort:    c.RelatedPort,
-		TCPType:        tcpType,
-	}, nil
-}
-
 // ToJSON returns an ICECandidateInit
 // as indicated by the spec https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson
 func (c ICECandidate) ToJSON() ICECandidateInit {
 	zeroVal := uint16(0)
 	emptyStr := ""
+	candidateStr := ""
+
+	candidate, err := c.toICE()
+	if err == nil {
+		candidateStr = candidate.Marshal()
+	}
 
 	return ICECandidateInit{
-		Candidate:     fmt.Sprintf("candidate:%s", iceCandidateToSDP(c).Marshal()),
+		Candidate:     fmt.Sprintf("candidate:%s", candidateStr),
 		SDPMid:        &emptyStr,
 		SDPMLineIndex: &zeroVal,
 	}
diff --git a/mediaengine.go b/mediaengine.go
index 80a7bb20..ed8974dc 100644
--- a/mediaengine.go
+++ b/mediaengine.go
@@ -9,7 +9,7 @@ import (
 
 	"github.com/pion/rtp"
 	"github.com/pion/rtp/codecs"
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 )
 
 // PayloadTypes for the default codecs
diff --git a/peerconnection.go b/peerconnection.go
index 24684955..3dfe88d5 100644
--- a/peerconnection.go
+++ b/peerconnection.go
@@ -14,9 +14,10 @@ import (
 	"sync"
 	"time"
 
+	"github.com/pion/ice/v2"
 	"github.com/pion/logging"
 	"github.com/pion/rtcp"
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 
 	"github.com/pion/webrtc/v3/internal/util"
 	"github.com/pion/webrtc/v3/pkg/rtcerr"
@@ -1411,13 +1412,12 @@ func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) error {
 	}
 
 	candidateValue := strings.TrimPrefix(candidate.Candidate, "candidate:")
-	attribute := sdp.NewAttribute("candidate", candidateValue)
-	sdpCandidate, err := attribute.ToICECandidate()
+	c, err := ice.UnmarshalCandidate(candidateValue)
 	if err != nil {
 		return err
 	}
 
-	iceCandidate, err := newICECandidateFromSDP(sdpCandidate)
+	iceCandidate, err := newICECandidateFromICE(c)
 	if err != nil {
 		return err
 	}
diff --git a/peerconnection_js.go b/peerconnection_js.go
index 5072cc3d..2d1ffb64 100644
--- a/peerconnection_js.go
+++ b/peerconnection_js.go
@@ -6,7 +6,7 @@ package webrtc
 import (
 	"syscall/js"
 
-	"github.com/pion/sdp/v2"
+	"github.com/pion/ice/v2"
 	"github.com/pion/webrtc/v3/pkg/rtcerr"
 )
 
@@ -570,15 +570,16 @@ func valueToICECandidate(val js.Value) *ICECandidate {
 	}
 	if jsValueIsUndefined(val.Get("protocol")) && !jsValueIsUndefined(val.Get("candidate")) {
 		// Missing some fields, assume it's Firefox and parse SDP candidate.
-		attribute := sdp.NewAttribute("candidate", val.Get("candidate").String())
-		sdpCandidate, err := attribute.ToICECandidate()
+		c, err := ice.UnmarshalCandidate(val.Get("candidate").String())
 		if err != nil {
 			return nil
 		}
-		iceCandidate, err := newICECandidateFromSDP(sdpCandidate)
+
+		iceCandidate, err := newICECandidateFromICE(c)
 		if err != nil {
 			return nil
 		}
+
 		return &iceCandidate
 	}
 	protocol, _ := NewICEProtocol(val.Get("protocol").String())
diff --git a/rtptransceiver.go b/rtptransceiver.go
index 505da951..076b8df9 100644
--- a/rtptransceiver.go
+++ b/rtptransceiver.go
@@ -7,7 +7,7 @@ import (
 	"sync/atomic"
 
 	"github.com/pion/rtp"
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 )
 
 // RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid.
diff --git a/sdp.go b/sdp.go
index 3ae5404d..d99ed831 100644
--- a/sdp.go
+++ b/sdp.go
@@ -8,8 +8,9 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/pion/ice/v2"
 	"github.com/pion/logging"
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 )
 
 // trackDetails represents any media source that can be represented in a SDP
@@ -181,8 +182,8 @@ func getRids(media *sdp.MediaDescription) map[string]string {
 	return rids
 }
 
-func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDescription, iceGatheringState ICEGatheringState) {
-	appendCandidateIfNew := func(c sdp.ICECandidate, attributes []sdp.Attribute) {
+func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDescription, iceGatheringState ICEGatheringState) error {
+	appendCandidateIfNew := func(c ice.Candidate, attributes []sdp.Attribute) {
 		marshaled := c.Marshal()
 		for _, a := range attributes {
 			if marshaled == a.Value {
@@ -190,32 +191,36 @@ func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDes
 			}
 		}
 
-		m.WithICECandidate(c)
+		m.WithValueAttribute("candidate", marshaled)
 	}
 
 	for _, c := range candidates {
-		sdpCandidate := iceCandidateToSDP(c)
-		sdpCandidate.ExtensionAttributes = append(sdpCandidate.ExtensionAttributes, sdp.ICECandidateAttribute{Key: "generation", Value: "0"})
-		sdpCandidate.Component = 1
-		appendCandidateIfNew(sdpCandidate, m.Attributes)
+		candidate, err := c.toICE()
+		if err != nil {
+			return err
+		}
+
+		candidate.SetComponent(1)
+		appendCandidateIfNew(candidate, m.Attributes)
 
-		sdpCandidate.Component = 2
-		appendCandidateIfNew(sdpCandidate, m.Attributes)
+		candidate.SetComponent(2)
+		appendCandidateIfNew(candidate, m.Attributes)
 	}
 
 	if iceGatheringState != ICEGatheringStateComplete {
-		return
+		return nil
 	}
 	for _, a := range m.Attributes {
 		if a.Key == "end-of-candidates" {
-			return
+			return nil
 		}
 	}
 
 	m.WithPropertyAttribute("end-of-candidates")
+	return nil
 }
 
-func addDataMediaSection(d *sdp.SessionDescription, dtlsFingerprints []DTLSFingerprint, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState) {
+func addDataMediaSection(d *sdp.SessionDescription, dtlsFingerprints []DTLSFingerprint, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState) error {
 	media := (&sdp.MediaDescription{
 		MediaName: sdp.MediaName{
 			Media:   mediaSectionApplication,
@@ -241,8 +246,12 @@ func addDataMediaSection(d *sdp.SessionDescription, dtlsFingerprints []DTLSFinge
 		media = media.WithFingerprint(f.Algorithm, strings.ToUpper(f.Value))
 	}
 
-	addCandidatesToMediaDescriptions(candidates, media, iceGatheringState)
+	if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
+		return err
+	}
+
 	d.WithMedia(media)
+	return nil
 }
 
 func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGatherer, iceGatheringState ICEGatheringState) *SessionDescription {
@@ -257,8 +266,11 @@ func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGathe
 
 	parsed := sessionDescription.parsed
 	for _, m := range parsed.MediaDescriptions {
-		addCandidatesToMediaDescriptions(candidates, m, iceGatheringState)
+		if err = addCandidatesToMediaDescriptions(candidates, m, iceGatheringState); err != nil {
+			return sessionDescription
+		}
 	}
+
 	sdp, err := parsed.Marshal()
 	if err != nil {
 		return sessionDescription
@@ -340,7 +352,9 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints
 		media = media.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
 	}
 
-	addCandidatesToMediaDescriptions(candidates, media, iceGatheringState)
+	if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
+		return false, err
+	}
 	d.WithMedia(media)
 
 	return true, nil
@@ -378,7 +392,9 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL
 
 		shouldAddID := true
 		if m.data {
-			addDataMediaSection(d, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState)
+			if err = addDataMediaSection(d, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState); err != nil {
+				return nil, err
+			}
 		} else {
 			shouldAddID, err = addTransceiverSDP(d, isPlanB, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, extMaps, m)
 			if err != nil {
@@ -496,12 +512,12 @@ func extractICEDetails(desc *sdp.SessionDescription) (string, string, []ICECandi
 
 		for _, a := range m.Attributes {
 			if a.IsICECandidate() {
-				sdpCandidate, err := a.ToICECandidate()
+				c, err := ice.UnmarshalCandidate(a.Value)
 				if err != nil {
 					return "", "", nil, err
 				}
 
-				candidate, err := newICECandidateFromSDP(sdpCandidate)
+				candidate, err := newICECandidateFromICE(c)
 				if err != nil {
 					return "", "", nil, err
 				}
diff --git a/sessiondescription.go b/sessiondescription.go
index f752a7da..4087ed71 100644
--- a/sessiondescription.go
+++ b/sessiondescription.go
@@ -1,7 +1,7 @@
 package webrtc
 
 import (
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 )
 
 // SessionDescription is used to expose local and remote session descriptions.
diff --git a/settingengine.go b/settingengine.go
index 3f380b2c..02ce53d6 100644
--- a/settingengine.go
+++ b/settingengine.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/pion/ice/v2"
 	"github.com/pion/logging"
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 	"github.com/pion/transport/vnet"
 )
 


Test patch:
diff --git a/dtlsrole_test.go b/dtlsrole_test.go
index 5d8dfa6d..0e7ad343 100644
--- a/dtlsrole_test.go
+++ b/dtlsrole_test.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/ice_go_test.go b/ice_go_test.go
deleted file mode 100644
index 365f66c1..00000000
--- a/ice_go_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// +build !js
-
-package webrtc
-
-import (
-	"testing"
-
-	"github.com/pion/sdp/v2"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestICECandidateToSDP(t *testing.T) {
-	testCases := []struct {
-		native ICECandidate
-
-		sdp sdp.ICECandidate
-	}{
-		{
-			ICECandidate{
-				Foundation: "foundation",
-				Priority:   128,
-				Address:    "1.0.0.1",
-				Protocol:   ICEProtocolUDP,
-				Port:       1234,
-				Typ:        ICECandidateTypeHost,
-				Component:  1,
-			},
-
-			sdp.ICECandidate{
-				Foundation: "foundation",
-				Priority:   128,
-				Address:    "1.0.0.1",
-				Protocol:   "udp",
-				Port:       1234,
-				Typ:        "host",
-				Component:  1,
-			},
-		},
-		{
-			ICECandidate{
-				Foundation:     "foundation",
-				Priority:       128,
-				Address:        "::1",
-				Protocol:       ICEProtocolUDP,
-				Port:           1234,
-				Typ:            ICECandidateTypeSrflx,
-				Component:      1,
-				RelatedAddress: "1.0.0.1",
-				RelatedPort:    4321,
-			},
-
-			sdp.ICECandidate{
-				Foundation:     "foundation",
-				Priority:       128,
-				Address:        "::1",
-				Protocol:       "udp",
-				Port:           1234,
-				Typ:            "srflx",
-				Component:      1,
-				RelatedAddress: "1.0.0.1",
-				RelatedPort:    4321,
-			},
-		},
-		{
-			ICECandidate{
-				Foundation:     "foundation",
-				Priority:       128,
-				Address:        "::1",
-				Protocol:       ICEProtocolUDP,
-				Port:           1234,
-				Typ:            ICECandidateTypePrflx,
-				Component:      1,
-				RelatedAddress: "1.0.0.1",
-				RelatedPort:    4321,
-			},
-
-			sdp.ICECandidate{
-				Foundation:     "foundation",
-				Priority:       128,
-				Address:        "::1",
-				Protocol:       "udp",
-				Port:           1234,
-				Typ:            "prflx",
-				Component:      1,
-				RelatedAddress: "1.0.0.1",
-				RelatedPort:    4321,
-			},
-		},
-	}
-
-	for i, testCase := range testCases {
-		actualSDP := iceCandidateToSDP(testCase.native)
-		assert.Equal(t,
-			testCase.sdp,
-			actualSDP,
-			"testCase: %d sdp not equal %v", i, actualSDP,
-		)
-	}
-}
diff --git a/icecandidate_test.go b/icecandidate_test.go
index c2b014b0..ed952516 100644
--- a/icecandidate_test.go
+++ b/icecandidate_test.go
@@ -4,9 +4,7 @@ import (
 	"testing"
 
 	"github.com/pion/ice/v2"
-	"github.com/pion/sdp/v2"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
 func TestICECandidate_Convert(t *testing.T) {
@@ -92,31 +90,37 @@ func TestICECandidate_Convert(t *testing.T) {
 		switch testCase.expectedType {
 		case ice.CandidateTypeHost:
 			config := ice.CandidateHostConfig{
-				Network:   testCase.expectedNetwork,
-				Address:   testCase.expectedAddress,
-				Port:      testCase.expectedPort,
-				Component: testCase.expectedComponent,
+				Network:    testCase.expectedNetwork,
+				Address:    testCase.expectedAddress,
+				Port:       testCase.expectedPort,
+				Component:  testCase.expectedComponent,
+				Foundation: "foundation",
+				Priority:   128,
 			}
 			expectedICE, err = ice.NewCandidateHost(&config)
 		case ice.CandidateTypeServerReflexive:
 			config := ice.CandidateServerReflexiveConfig{
-				Network:   testCase.expectedNetwork,
-				Address:   testCase.expectedAddress,
-				Port:      testCase.expectedPort,
-				Component: testCase.expectedComponent,
-				RelAddr:   testCase.expectedRelatedAddress.Address,
-				RelPort:   testCase.expectedRelatedAddress.Port,
+				Network:    testCase.expectedNetwork,
+				Address:    testCase.expectedAddress,
+				Port:       testCase.expectedPort,
+				Component:  testCase.expectedComponent,
+				Foundation: "foundation",
+				Priority:   128,
+				RelAddr:    testCase.expectedRelatedAddress.Address,
+				RelPort:    testCase.expectedRelatedAddress.Port,
 			}
 			expectedICE, err = ice.NewCandidateServerReflexive(&config)
 			assert.NoError(t, err)
 		case ice.CandidateTypePeerReflexive:
 			config := ice.CandidatePeerReflexiveConfig{
-				Network:   testCase.expectedNetwork,
-				Address:   testCase.expectedAddress,
-				Port:      testCase.expectedPort,
-				Component: testCase.expectedComponent,
-				RelAddr:   testCase.expectedRelatedAddress.Address,
-				RelPort:   testCase.expectedRelatedAddress.Port,
+				Network:    testCase.expectedNetwork,
+				Address:    testCase.expectedAddress,
+				Port:       testCase.expectedPort,
+				Component:  testCase.expectedComponent,
+				Foundation: "foundation",
+				Priority:   128,
+				RelAddr:    testCase.expectedRelatedAddress.Address,
+				RelPort:    testCase.expectedRelatedAddress.Port,
 			}
 			expectedICE, err = ice.NewCandidatePeerReflexive(&config)
 		}
@@ -131,47 +135,6 @@ func TestICECandidate_Convert(t *testing.T) {
 	}
 }
 
-func TestICECandidate_ConvertTCP(t *testing.T) {
-	candidate := ICECandidate{
-		Foundation: "foundation",
-		Priority:   128,
-		Address:    "1.0.0.1",
-		Protocol:   ICEProtocolTCP,
-		Port:       1234,
-		Typ:        ICECandidateTypeHost,
-		Component:  1,
-		TCPType:    "passive",
-	}
-
-	got, err := candidate.toICE()
-	require.NoError(t, err)
-
-	want, err := ice.NewCandidateHost(&ice.CandidateHostConfig{
-		CandidateID: got.ID(),
-		Address:     "1.0.0.1",
-		Component:   1,
-		Network:     "tcp",
-		Port:        1234,
-		TCPType:     ice.TCPTypePassive,
-	})
-	require.NoError(t, err)
-
-	assert.Equal(t, want, got)
-
-	sdpCandidate := iceCandidateToSDP(candidate)
-	assert.Equal(t, []sdp.ICECandidateAttribute{
-		{
-			Key:   "tcptype",
-			Value: "passive",
-		},
-	}, sdpCandidate.ExtensionAttributes)
-
-	candidate2, err := newICECandidateFromSDP(sdpCandidate)
-	require.NoError(t, err)
-
-	assert.Equal(t, candidate, candidate2)
-}
-
 func TestConvertTypeFromICE(t *testing.T) {
 	t.Run("host", func(t *testing.T) {
 		ct, err := convertTypeFromICE(ice.CandidateTypeHost)
diff --git a/mediaengine_test.go b/mediaengine_test.go
index a0998f7a..ee9dcb0c 100644
--- a/mediaengine_test.go
+++ b/mediaengine_test.go
@@ -7,7 +7,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/peerconnection_go_test.go b/peerconnection_go_test.go
index 0c4d709d..8a0905e4 100644
--- a/peerconnection_go_test.go
+++ b/peerconnection_go_test.go
@@ -828,6 +828,32 @@ func TestPopulateLocalCandidates(t *testing.T) {
 	})
 }
 
+// Assert that two agents that only generate mDNS candidates can connect
+func TestMulticastDNSCandidates(t *testing.T) {
+	lim := test.TimeOut(time.Second * 30)
+	defer lim.Stop()
+
+	report := test.CheckRoutines(t)
+	defer report()
+
+	s := SettingEngine{}
+	s.GenerateMulticastDNSCandidates(true)
+
+	pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(s)).newPair(Configuration{})
+	assert.NoError(t, err)
+
+	assert.NoError(t, signalPair(pcOffer, pcAnswer))
+
+	onDataChannel, onDataChannelCancel := context.WithCancel(context.Background())
+	pcAnswer.OnDataChannel(func(d *DataChannel) {
+		onDataChannelCancel()
+	})
+	<-onDataChannel.Done()
+
+	assert.NoError(t, pcOffer.Close())
+	assert.NoError(t, pcAnswer.Close())
+}
+
 func TestICERestart(t *testing.T) {
 	extractCandidates := func(sdp string) (candidates []string) {
 		sc := bufio.NewScanner(strings.NewReader(sdp))
diff --git a/peerconnection_js_test.go b/peerconnection_js_test.go
index 9f903e3e..6c7d8ebb 100644
--- a/peerconnection_js_test.go
+++ b/peerconnection_js_test.go
@@ -64,6 +64,8 @@ func TestValueToICECandidate(t *testing.T) {
 		if err != nil {
 			t.Errorf("Case %d: bad test, got error: %v", i, err)
 		}
-		assert.Equal(t, testCase.expect, *valueToICECandidate(js.ValueOf(v)))
+		val := *valueToICECandidate(js.ValueOf(v))
+		val.statsID = ""
+		assert.Equal(t, testCase.expect, val)
 	}
 }
diff --git a/peerconnection_media_test.go b/peerconnection_media_test.go
index daad2354..df925627 100644
--- a/peerconnection_media_test.go
+++ b/peerconnection_media_test.go
@@ -15,7 +15,7 @@ import (
 	"time"
 
 	"github.com/pion/rtcp"
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 	"github.com/pion/transport/test"
 	"github.com/pion/webrtc/v3/pkg/media"
 	"github.com/stretchr/testify/assert"
diff --git a/sdp_test.go b/sdp_test.go
index 89c1b71c..757e925d 100644
--- a/sdp_test.go
+++ b/sdp_test.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/sdpsemantics_test.go b/sdpsemantics_test.go
index 405f0e72..409a97ca 100644
--- a/sdpsemantics_test.go
+++ b/sdpsemantics_test.go
@@ -7,7 +7,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/pion/sdp/v2"
+	"github.com/pion/sdp/v3"
 	"github.com/pion/transport/test"
 
 	"github.com/stretchr/testify/assert"