sdp.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
  2. // SPDX-License-Identifier: MIT
  3. //go:build !js
  4. // +build !js
  5. package webrtc
  6. import (
  7. "errors"
  8. "fmt"
  9. "net/url"
  10. "regexp"
  11. "strconv"
  12. "strings"
  13. "sync/atomic"
  14. "github.com/pion/ice/v2"
  15. "github.com/pion/logging"
  16. "github.com/pion/sdp/v3"
  17. )
  18. // trackDetails represents any media source that can be represented in a SDP
  19. // This isn't keyed by SSRC because it also needs to support rid based sources
  20. type trackDetails struct {
  21. mid string
  22. kind RTPCodecType
  23. streamID string
  24. id string
  25. ssrcs []SSRC
  26. repairSsrc *SSRC
  27. rids []string
  28. }
  29. func trackDetailsForSSRC(trackDetails []trackDetails, ssrc SSRC) *trackDetails {
  30. for i := range trackDetails {
  31. for j := range trackDetails[i].ssrcs {
  32. if trackDetails[i].ssrcs[j] == ssrc {
  33. return &trackDetails[i]
  34. }
  35. }
  36. }
  37. return nil
  38. }
  39. func trackDetailsForRID(trackDetails []trackDetails, mid, rid string) *trackDetails {
  40. for i := range trackDetails {
  41. if trackDetails[i].mid != mid {
  42. continue
  43. }
  44. for j := range trackDetails[i].rids {
  45. if trackDetails[i].rids[j] == rid {
  46. return &trackDetails[i]
  47. }
  48. }
  49. }
  50. return nil
  51. }
  52. func filterTrackWithSSRC(incomingTracks []trackDetails, ssrc SSRC) []trackDetails {
  53. filtered := []trackDetails{}
  54. doesTrackHaveSSRC := func(t trackDetails) bool {
  55. for i := range t.ssrcs {
  56. if t.ssrcs[i] == ssrc {
  57. return true
  58. }
  59. }
  60. return false
  61. }
  62. for i := range incomingTracks {
  63. if !doesTrackHaveSSRC(incomingTracks[i]) {
  64. filtered = append(filtered, incomingTracks[i])
  65. }
  66. }
  67. return filtered
  68. }
  69. // extract all trackDetails from an SDP.
  70. func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) (incomingTracks []trackDetails) { // nolint:gocognit
  71. for _, media := range s.MediaDescriptions {
  72. tracksInMediaSection := []trackDetails{}
  73. rtxRepairFlows := map[uint64]uint64{}
  74. // Plan B can have multiple tracks in a signle media section
  75. streamID := ""
  76. trackID := ""
  77. // If media section is recvonly or inactive skip
  78. if _, ok := media.Attribute(sdp.AttrKeyRecvOnly); ok {
  79. continue
  80. } else if _, ok := media.Attribute(sdp.AttrKeyInactive); ok {
  81. continue
  82. }
  83. midValue := getMidValue(media)
  84. if midValue == "" {
  85. continue
  86. }
  87. codecType := NewRTPCodecType(media.MediaName.Media)
  88. if codecType == 0 {
  89. continue
  90. }
  91. for _, attr := range media.Attributes {
  92. switch attr.Key {
  93. case sdp.AttrKeySSRCGroup:
  94. split := strings.Split(attr.Value, " ")
  95. if split[0] == sdp.SemanticTokenFlowIdentification {
  96. // Add rtx ssrcs to blacklist, to avoid adding them as tracks
  97. // Essentially lines like `a=ssrc-group:FID 2231627014 632943048` are processed by this section
  98. // as this declares that the second SSRC (632943048) is a rtx repair flow (RFC4588) for the first
  99. // (2231627014) as specified in RFC5576
  100. if len(split) == 3 {
  101. baseSsrc, err := strconv.ParseUint(split[1], 10, 32)
  102. if err != nil {
  103. log.Warnf("Failed to parse SSRC: %v", err)
  104. continue
  105. }
  106. rtxRepairFlow, err := strconv.ParseUint(split[2], 10, 32)
  107. if err != nil {
  108. log.Warnf("Failed to parse SSRC: %v", err)
  109. continue
  110. }
  111. rtxRepairFlows[rtxRepairFlow] = baseSsrc
  112. tracksInMediaSection = filterTrackWithSSRC(tracksInMediaSection, SSRC(rtxRepairFlow)) // Remove if rtx was added as track before
  113. for i := range tracksInMediaSection {
  114. if tracksInMediaSection[i].ssrcs[0] == SSRC(baseSsrc) {
  115. repairSsrc := SSRC(rtxRepairFlow)
  116. tracksInMediaSection[i].repairSsrc = &repairSsrc
  117. }
  118. }
  119. }
  120. }
  121. // Handle `a=msid:<stream_id> <track_label>` for Unified plan. The first value is the same as MediaStream.id
  122. // in the browser and can be used to figure out which tracks belong to the same stream. The browser should
  123. // figure this out automatically when an ontrack event is emitted on RTCPeerConnection.
  124. case sdp.AttrKeyMsid:
  125. split := strings.Split(attr.Value, " ")
  126. if len(split) == 2 {
  127. streamID = split[0]
  128. trackID = split[1]
  129. }
  130. case sdp.AttrKeySSRC:
  131. split := strings.Split(attr.Value, " ")
  132. ssrc, err := strconv.ParseUint(split[0], 10, 32)
  133. if err != nil {
  134. log.Warnf("Failed to parse SSRC: %v", err)
  135. continue
  136. }
  137. if _, ok := rtxRepairFlows[ssrc]; ok {
  138. continue // This ssrc is a RTX repair flow, ignore
  139. }
  140. if len(split) == 3 && strings.HasPrefix(split[1], "msid:") {
  141. streamID = split[1][len("msid:"):]
  142. trackID = split[2]
  143. }
  144. isNewTrack := true
  145. trackDetails := &trackDetails{}
  146. for i := range tracksInMediaSection {
  147. for j := range tracksInMediaSection[i].ssrcs {
  148. if tracksInMediaSection[i].ssrcs[j] == SSRC(ssrc) {
  149. trackDetails = &tracksInMediaSection[i]
  150. isNewTrack = false
  151. }
  152. }
  153. }
  154. trackDetails.mid = midValue
  155. trackDetails.kind = codecType
  156. trackDetails.streamID = streamID
  157. trackDetails.id = trackID
  158. trackDetails.ssrcs = []SSRC{SSRC(ssrc)}
  159. for r, baseSsrc := range rtxRepairFlows {
  160. if baseSsrc == ssrc {
  161. repairSsrc := SSRC(r)
  162. trackDetails.repairSsrc = &repairSsrc
  163. }
  164. }
  165. if isNewTrack {
  166. tracksInMediaSection = append(tracksInMediaSection, *trackDetails)
  167. }
  168. }
  169. }
  170. if rids := getRids(media); len(rids) != 0 && trackID != "" && streamID != "" {
  171. simulcastTrack := trackDetails{
  172. mid: midValue,
  173. kind: codecType,
  174. streamID: streamID,
  175. id: trackID,
  176. rids: []string{},
  177. }
  178. for rid := range rids {
  179. simulcastTrack.rids = append(simulcastTrack.rids, rid)
  180. }
  181. tracksInMediaSection = []trackDetails{simulcastTrack}
  182. }
  183. incomingTracks = append(incomingTracks, tracksInMediaSection...)
  184. }
  185. return incomingTracks
  186. }
  187. func trackDetailsToRTPReceiveParameters(t *trackDetails) RTPReceiveParameters {
  188. encodingSize := len(t.ssrcs)
  189. if len(t.rids) >= encodingSize {
  190. encodingSize = len(t.rids)
  191. }
  192. encodings := make([]RTPDecodingParameters, encodingSize)
  193. for i := range encodings {
  194. if len(t.rids) > i {
  195. encodings[i].RID = t.rids[i]
  196. }
  197. if len(t.ssrcs) > i {
  198. encodings[i].SSRC = t.ssrcs[i]
  199. }
  200. if t.repairSsrc != nil {
  201. encodings[i].RTX.SSRC = *t.repairSsrc
  202. }
  203. }
  204. return RTPReceiveParameters{Encodings: encodings}
  205. }
  206. func getRids(media *sdp.MediaDescription) map[string]*simulcastRid {
  207. rids := map[string]*simulcastRid{}
  208. var simulcastAttr string
  209. for _, attr := range media.Attributes {
  210. if attr.Key == sdpAttributeRid {
  211. split := strings.Split(attr.Value, " ")
  212. rids[split[0]] = &simulcastRid{attrValue: attr.Value}
  213. } else if attr.Key == sdpAttributeSimulcast {
  214. simulcastAttr = attr.Value
  215. }
  216. }
  217. // process paused stream like "a=simulcast:send 1;~2;~3"
  218. if simulcastAttr != "" {
  219. if space := strings.Index(simulcastAttr, " "); space > 0 {
  220. simulcastAttr = simulcastAttr[space+1:]
  221. }
  222. ridStates := strings.Split(simulcastAttr, ";")
  223. for _, ridState := range ridStates {
  224. if ridState[:1] == "~" {
  225. rid := ridState[1:]
  226. if r, ok := rids[rid]; ok {
  227. r.paused = true
  228. }
  229. }
  230. }
  231. }
  232. return rids
  233. }
  234. func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDescription, iceGatheringState ICEGatheringState) error {
  235. appendCandidateIfNew := func(c ice.Candidate, attributes []sdp.Attribute) {
  236. marshaled := c.Marshal()
  237. for _, a := range attributes {
  238. if marshaled == a.Value {
  239. return
  240. }
  241. }
  242. m.WithValueAttribute("candidate", marshaled)
  243. }
  244. for _, c := range candidates {
  245. candidate, err := c.toICE()
  246. if err != nil {
  247. return err
  248. }
  249. candidate.SetComponent(1)
  250. appendCandidateIfNew(candidate, m.Attributes)
  251. candidate.SetComponent(2)
  252. appendCandidateIfNew(candidate, m.Attributes)
  253. }
  254. if iceGatheringState != ICEGatheringStateComplete {
  255. return nil
  256. }
  257. for _, a := range m.Attributes {
  258. if a.Key == "end-of-candidates" {
  259. return nil
  260. }
  261. }
  262. m.WithPropertyAttribute("end-of-candidates")
  263. return nil
  264. }
  265. func addDataMediaSection(d *sdp.SessionDescription, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState) error {
  266. media := (&sdp.MediaDescription{
  267. MediaName: sdp.MediaName{
  268. Media: mediaSectionApplication,
  269. Port: sdp.RangedPort{Value: 9},
  270. Protos: []string{"UDP", "DTLS", "SCTP"},
  271. Formats: []string{"webrtc-datachannel"},
  272. },
  273. ConnectionInformation: &sdp.ConnectionInformation{
  274. NetworkType: "IN",
  275. AddressType: "IP4",
  276. Address: &sdp.Address{
  277. Address: "0.0.0.0",
  278. },
  279. },
  280. }).
  281. WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()).
  282. WithValueAttribute(sdp.AttrKeyMID, midValue).
  283. WithPropertyAttribute(RTPTransceiverDirectionSendrecv.String()).
  284. WithPropertyAttribute("sctp-port:5000").
  285. WithICECredentials(iceParams.UsernameFragment, iceParams.Password)
  286. for _, f := range dtlsFingerprints {
  287. media = media.WithFingerprint(f.Algorithm, strings.ToUpper(f.Value))
  288. }
  289. if shouldAddCandidates {
  290. if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
  291. return err
  292. }
  293. }
  294. d.WithMedia(media)
  295. return nil
  296. }
  297. func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGatherer, iceGatheringState ICEGatheringState) *SessionDescription {
  298. if sessionDescription == nil || i == nil {
  299. return sessionDescription
  300. }
  301. candidates, err := i.GetLocalCandidates()
  302. if err != nil {
  303. return sessionDescription
  304. }
  305. parsed := sessionDescription.parsed
  306. if len(parsed.MediaDescriptions) > 0 {
  307. m := parsed.MediaDescriptions[0]
  308. if err = addCandidatesToMediaDescriptions(candidates, m, iceGatheringState); err != nil {
  309. return sessionDescription
  310. }
  311. }
  312. sdp, err := parsed.Marshal()
  313. if err != nil {
  314. return sessionDescription
  315. }
  316. return &SessionDescription{
  317. SDP: string(sdp),
  318. Type: sessionDescription.Type,
  319. parsed: parsed,
  320. }
  321. }
  322. func addSenderSDP(
  323. mediaSection mediaSection,
  324. isPlanB bool,
  325. media *sdp.MediaDescription,
  326. ) {
  327. for _, mt := range mediaSection.transceivers {
  328. sender := mt.Sender()
  329. if sender == nil {
  330. continue
  331. }
  332. track := sender.Track()
  333. if track == nil {
  334. continue
  335. }
  336. sendParameters := sender.GetParameters()
  337. for _, encoding := range sendParameters.Encodings {
  338. media = media.WithMediaSource(uint32(encoding.SSRC), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
  339. if !isPlanB {
  340. media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
  341. }
  342. }
  343. if len(sendParameters.Encodings) > 1 {
  344. sendRids := make([]string, 0, len(sendParameters.Encodings))
  345. for _, encoding := range sendParameters.Encodings {
  346. media.WithValueAttribute(sdpAttributeRid, encoding.RID+" send")
  347. sendRids = append(sendRids, encoding.RID)
  348. }
  349. // Simulcast
  350. media.WithValueAttribute(sdpAttributeSimulcast, "send "+strings.Join(sendRids, ";"))
  351. }
  352. if !isPlanB {
  353. break
  354. }
  355. }
  356. }
  357. func addTransceiverSDP(
  358. d *sdp.SessionDescription,
  359. isPlanB bool,
  360. shouldAddCandidates bool,
  361. dtlsFingerprints []DTLSFingerprint,
  362. mediaEngine *MediaEngine,
  363. midValue string,
  364. iceParams ICEParameters,
  365. candidates []ICECandidate,
  366. dtlsRole sdp.ConnectionRole,
  367. iceGatheringState ICEGatheringState,
  368. mediaSection mediaSection,
  369. ) (bool, error) {
  370. transceivers := mediaSection.transceivers
  371. if len(transceivers) < 1 {
  372. return false, errSDPZeroTransceivers
  373. }
  374. // Use the first transceiver to generate the section attributes
  375. t := transceivers[0]
  376. media := sdp.NewJSEPMediaDescription(t.kind.String(), []string{}).
  377. WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()).
  378. WithValueAttribute(sdp.AttrKeyMID, midValue).
  379. WithICECredentials(iceParams.UsernameFragment, iceParams.Password).
  380. WithPropertyAttribute(sdp.AttrKeyRTCPMux).
  381. WithPropertyAttribute(sdp.AttrKeyRTCPRsize)
  382. codecs := t.getCodecs()
  383. for _, codec := range codecs {
  384. name := strings.TrimPrefix(codec.MimeType, "audio/")
  385. name = strings.TrimPrefix(name, "video/")
  386. media.WithCodec(uint8(codec.PayloadType), name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
  387. for _, feedback := range codec.RTPCodecCapability.RTCPFeedback {
  388. media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter))
  389. }
  390. }
  391. if len(codecs) == 0 {
  392. // If we are sender and we have no codecs throw an error early
  393. if t.Sender() != nil {
  394. return false, ErrSenderWithNoCodecs
  395. }
  396. // Explicitly reject track if we don't have the codec
  397. // We need to include connection information even if we're rejecting a track, otherwise Firefox will fail to
  398. // parse the SDP with an error like:
  399. // SIPCC Failed to parse SDP: SDP Parse Error on line 50: c= connection line not specified for every media level, validation failed.
  400. // In addition this makes our SDP compliant with RFC 4566 Section 5.7: https://datatracker.ietf.org/doc/html/rfc4566#section-5.7
  401. d.WithMedia(&sdp.MediaDescription{
  402. MediaName: sdp.MediaName{
  403. Media: t.kind.String(),
  404. Port: sdp.RangedPort{Value: 0},
  405. Protos: []string{"UDP", "TLS", "RTP", "SAVPF"},
  406. Formats: []string{"0"},
  407. },
  408. ConnectionInformation: &sdp.ConnectionInformation{
  409. NetworkType: "IN",
  410. AddressType: "IP4",
  411. Address: &sdp.Address{
  412. Address: "0.0.0.0",
  413. },
  414. },
  415. })
  416. return false, nil
  417. }
  418. directions := []RTPTransceiverDirection{}
  419. if t.Sender() != nil {
  420. directions = append(directions, RTPTransceiverDirectionSendonly)
  421. }
  422. if t.Receiver() != nil {
  423. directions = append(directions, RTPTransceiverDirectionRecvonly)
  424. }
  425. parameters := mediaEngine.getRTPParametersByKind(t.kind, directions)
  426. for _, rtpExtension := range parameters.HeaderExtensions {
  427. extURL, err := url.Parse(rtpExtension.URI)
  428. if err != nil {
  429. return false, err
  430. }
  431. media.WithExtMap(sdp.ExtMap{Value: rtpExtension.ID, URI: extURL})
  432. }
  433. if len(mediaSection.ridMap) > 0 {
  434. recvRids := make([]string, 0, len(mediaSection.ridMap))
  435. for rid := range mediaSection.ridMap {
  436. media.WithValueAttribute(sdpAttributeRid, rid+" recv")
  437. if mediaSection.ridMap[rid].paused {
  438. rid = "~" + rid
  439. }
  440. recvRids = append(recvRids, rid)
  441. }
  442. // Simulcast
  443. media.WithValueAttribute(sdpAttributeSimulcast, "recv "+strings.Join(recvRids, ";"))
  444. }
  445. addSenderSDP(mediaSection, isPlanB, media)
  446. media = media.WithPropertyAttribute(t.Direction().String())
  447. for _, fingerprint := range dtlsFingerprints {
  448. media = media.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
  449. }
  450. if shouldAddCandidates {
  451. if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil {
  452. return false, err
  453. }
  454. }
  455. d.WithMedia(media)
  456. return true, nil
  457. }
  458. type simulcastRid struct {
  459. attrValue string
  460. paused bool
  461. }
  462. type mediaSection struct {
  463. id string
  464. transceivers []*RTPTransceiver
  465. data bool
  466. ridMap map[string]*simulcastRid
  467. }
  468. func bundleMatchFromRemote(matchBundleGroup *string) func(mid string) bool {
  469. if matchBundleGroup == nil {
  470. return func(midValue string) bool {
  471. return true
  472. }
  473. }
  474. bundleTags := strings.Split(*matchBundleGroup, " ")
  475. return func(midValue string) bool {
  476. for _, tag := range bundleTags {
  477. if tag == midValue {
  478. return true
  479. }
  480. }
  481. return false
  482. }
  483. }
  484. // populateSDP serializes a PeerConnections state into an SDP
  485. func populateSDP(
  486. d *sdp.SessionDescription,
  487. isPlanB bool,
  488. dtlsFingerprints []DTLSFingerprint,
  489. mediaDescriptionFingerprint bool,
  490. isICELite bool,
  491. isExtmapAllowMixed bool,
  492. mediaEngine *MediaEngine,
  493. connectionRole sdp.ConnectionRole,
  494. candidates []ICECandidate,
  495. iceParams ICEParameters,
  496. mediaSections []mediaSection,
  497. iceGatheringState ICEGatheringState,
  498. matchBundleGroup *string,
  499. ) (*sdp.SessionDescription, error) {
  500. var err error
  501. mediaDtlsFingerprints := []DTLSFingerprint{}
  502. if mediaDescriptionFingerprint {
  503. mediaDtlsFingerprints = dtlsFingerprints
  504. }
  505. bundleValue := "BUNDLE"
  506. bundleCount := 0
  507. bundleMatch := bundleMatchFromRemote(matchBundleGroup)
  508. appendBundle := func(midValue string) {
  509. bundleValue += " " + midValue
  510. bundleCount++
  511. }
  512. for i, m := range mediaSections {
  513. if m.data && len(m.transceivers) != 0 {
  514. return nil, errSDPMediaSectionMediaDataChanInvalid
  515. } else if !isPlanB && len(m.transceivers) > 1 {
  516. return nil, errSDPMediaSectionMultipleTrackInvalid
  517. }
  518. shouldAddID := true
  519. shouldAddCandidates := i == 0
  520. if m.data {
  521. if err = addDataMediaSection(d, shouldAddCandidates, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState); err != nil {
  522. return nil, err
  523. }
  524. } else {
  525. shouldAddID, err = addTransceiverSDP(d, isPlanB, shouldAddCandidates, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, m)
  526. if err != nil {
  527. return nil, err
  528. }
  529. }
  530. if shouldAddID {
  531. if bundleMatch(m.id) {
  532. appendBundle(m.id)
  533. } else {
  534. d.MediaDescriptions[len(d.MediaDescriptions)-1].MediaName.Port = sdp.RangedPort{Value: 0}
  535. }
  536. }
  537. }
  538. if !mediaDescriptionFingerprint {
  539. for _, fingerprint := range dtlsFingerprints {
  540. d.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value))
  541. }
  542. }
  543. if isICELite {
  544. // RFC 5245 S15.3
  545. d = d.WithValueAttribute(sdp.AttrKeyICELite, "")
  546. }
  547. if isExtmapAllowMixed {
  548. d = d.WithPropertyAttribute(sdp.AttrKeyExtMapAllowMixed)
  549. }
  550. if bundleCount > 0 {
  551. d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue)
  552. }
  553. return d, nil
  554. }
  555. func getMidValue(media *sdp.MediaDescription) string {
  556. for _, attr := range media.Attributes {
  557. if attr.Key == "mid" {
  558. return attr.Value
  559. }
  560. }
  561. return ""
  562. }
  563. // SessionDescription contains a MediaSection with Multiple SSRCs, it is Plan-B
  564. func descriptionIsPlanB(desc *SessionDescription, log logging.LeveledLogger) bool {
  565. if desc == nil || desc.parsed == nil {
  566. return false
  567. }
  568. // Store all MIDs that already contain a track
  569. midWithTrack := map[string]bool{}
  570. for _, trackDetail := range trackDetailsFromSDP(log, desc.parsed) {
  571. if _, ok := midWithTrack[trackDetail.mid]; ok {
  572. return true
  573. }
  574. midWithTrack[trackDetail.mid] = true
  575. }
  576. return false
  577. }
  578. // SessionDescription contains a MediaSection with name `audio`, `video` or `data`
  579. // If only one SSRC is set we can't know if it is Plan-B or Unified. If users have
  580. // set fallback mode assume it is Plan-B
  581. func descriptionPossiblyPlanB(desc *SessionDescription) bool {
  582. if desc == nil || desc.parsed == nil {
  583. return false
  584. }
  585. detectionRegex := regexp.MustCompile(`(?i)^(audio|video|data)$`)
  586. for _, media := range desc.parsed.MediaDescriptions {
  587. if len(detectionRegex.FindStringSubmatch(getMidValue(media))) == 2 {
  588. return true
  589. }
  590. }
  591. return false
  592. }
  593. func getPeerDirection(media *sdp.MediaDescription) RTPTransceiverDirection {
  594. for _, a := range media.Attributes {
  595. if direction := NewRTPTransceiverDirection(a.Key); direction != RTPTransceiverDirection(Unknown) {
  596. return direction
  597. }
  598. }
  599. return RTPTransceiverDirection(Unknown)
  600. }
  601. func extractFingerprint(desc *sdp.SessionDescription) (string, string, error) {
  602. fingerprints := []string{}
  603. if fingerprint, haveFingerprint := desc.Attribute("fingerprint"); haveFingerprint {
  604. fingerprints = append(fingerprints, fingerprint)
  605. }
  606. for _, m := range desc.MediaDescriptions {
  607. if fingerprint, haveFingerprint := m.Attribute("fingerprint"); haveFingerprint {
  608. fingerprints = append(fingerprints, fingerprint)
  609. }
  610. }
  611. if len(fingerprints) < 1 {
  612. return "", "", ErrSessionDescriptionNoFingerprint
  613. }
  614. for _, m := range fingerprints {
  615. if m != fingerprints[0] {
  616. return "", "", ErrSessionDescriptionConflictingFingerprints
  617. }
  618. }
  619. parts := strings.Split(fingerprints[0], " ")
  620. if len(parts) != 2 {
  621. return "", "", ErrSessionDescriptionInvalidFingerprint
  622. }
  623. return parts[1], parts[0], nil
  624. }
  625. func extractICEDetails(desc *sdp.SessionDescription, log logging.LeveledLogger) (string, string, []ICECandidate, error) { // nolint:gocognit
  626. candidates := []ICECandidate{}
  627. remotePwds := []string{}
  628. remoteUfrags := []string{}
  629. if ufrag, haveUfrag := desc.Attribute("ice-ufrag"); haveUfrag {
  630. remoteUfrags = append(remoteUfrags, ufrag)
  631. }
  632. if pwd, havePwd := desc.Attribute("ice-pwd"); havePwd {
  633. remotePwds = append(remotePwds, pwd)
  634. }
  635. for _, m := range desc.MediaDescriptions {
  636. if ufrag, haveUfrag := m.Attribute("ice-ufrag"); haveUfrag {
  637. remoteUfrags = append(remoteUfrags, ufrag)
  638. }
  639. if pwd, havePwd := m.Attribute("ice-pwd"); havePwd {
  640. remotePwds = append(remotePwds, pwd)
  641. }
  642. for _, a := range m.Attributes {
  643. if a.IsICECandidate() {
  644. c, err := ice.UnmarshalCandidate(a.Value)
  645. if err != nil {
  646. if errors.Is(err, ice.ErrUnknownCandidateTyp) || errors.Is(err, ice.ErrDetermineNetworkType) {
  647. log.Warnf("Discarding remote candidate: %s", err)
  648. continue
  649. }
  650. return "", "", nil, err
  651. }
  652. candidate, err := newICECandidateFromICE(c)
  653. if err != nil {
  654. return "", "", nil, err
  655. }
  656. candidates = append(candidates, candidate)
  657. }
  658. }
  659. }
  660. if len(remoteUfrags) == 0 {
  661. return "", "", nil, ErrSessionDescriptionMissingIceUfrag
  662. } else if len(remotePwds) == 0 {
  663. return "", "", nil, ErrSessionDescriptionMissingIcePwd
  664. }
  665. for _, m := range remoteUfrags {
  666. if m != remoteUfrags[0] {
  667. return "", "", nil, ErrSessionDescriptionConflictingIceUfrag
  668. }
  669. }
  670. for _, m := range remotePwds {
  671. if m != remotePwds[0] {
  672. return "", "", nil, ErrSessionDescriptionConflictingIcePwd
  673. }
  674. }
  675. return remoteUfrags[0], remotePwds[0], candidates, nil
  676. }
  677. func haveApplicationMediaSection(desc *sdp.SessionDescription) bool {
  678. for _, m := range desc.MediaDescriptions {
  679. if m.MediaName.Media == mediaSectionApplication {
  680. return true
  681. }
  682. }
  683. return false
  684. }
  685. func getByMid(searchMid string, desc *SessionDescription) *sdp.MediaDescription {
  686. for _, m := range desc.parsed.MediaDescriptions {
  687. if mid, ok := m.Attribute(sdp.AttrKeyMID); ok && mid == searchMid {
  688. return m
  689. }
  690. }
  691. return nil
  692. }
  693. // haveDataChannel return MediaDescription with MediaName equal application
  694. func haveDataChannel(desc *SessionDescription) *sdp.MediaDescription {
  695. for _, d := range desc.parsed.MediaDescriptions {
  696. if d.MediaName.Media == mediaSectionApplication {
  697. return d
  698. }
  699. }
  700. return nil
  701. }
  702. func codecsFromMediaDescription(m *sdp.MediaDescription) (out []RTPCodecParameters, err error) {
  703. s := &sdp.SessionDescription{
  704. MediaDescriptions: []*sdp.MediaDescription{m},
  705. }
  706. for _, payloadStr := range m.MediaName.Formats {
  707. payloadType, err := strconv.ParseUint(payloadStr, 10, 8)
  708. if err != nil {
  709. return nil, err
  710. }
  711. codec, err := s.GetCodecForPayloadType(uint8(payloadType))
  712. if err != nil {
  713. if payloadType == 0 {
  714. continue
  715. }
  716. return nil, err
  717. }
  718. channels := uint16(0)
  719. val, err := strconv.ParseUint(codec.EncodingParameters, 10, 16)
  720. if err == nil {
  721. channels = uint16(val)
  722. }
  723. feedback := []RTCPFeedback{}
  724. for _, raw := range codec.RTCPFeedback {
  725. split := strings.Split(raw, " ")
  726. entry := RTCPFeedback{Type: split[0]}
  727. if len(split) == 2 {
  728. entry.Parameter = split[1]
  729. }
  730. feedback = append(feedback, entry)
  731. }
  732. out = append(out, RTPCodecParameters{
  733. RTPCodecCapability: RTPCodecCapability{m.MediaName.Media + "/" + codec.Name, codec.ClockRate, channels, codec.Fmtp, feedback},
  734. PayloadType: PayloadType(payloadType),
  735. })
  736. }
  737. return out, nil
  738. }
  739. func rtpExtensionsFromMediaDescription(m *sdp.MediaDescription) (map[string]int, error) {
  740. out := map[string]int{}
  741. for _, a := range m.Attributes {
  742. if a.Key == sdp.AttrKeyExtMap {
  743. e := sdp.ExtMap{}
  744. if err := e.Unmarshal(a.String()); err != nil {
  745. return nil, err
  746. }
  747. out[e.URI.String()] = e.Value
  748. }
  749. }
  750. return out, nil
  751. }
  752. // updateSDPOrigin saves sdp.Origin in PeerConnection when creating 1st local SDP;
  753. // for subsequent calling, it updates Origin for SessionDescription from saved one
  754. // and increments session version by one.
  755. // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-25#section-5.2.2
  756. func updateSDPOrigin(origin *sdp.Origin, d *sdp.SessionDescription) {
  757. if atomic.CompareAndSwapUint64(&origin.SessionVersion, 0, d.Origin.SessionVersion) { // store
  758. atomic.StoreUint64(&origin.SessionID, d.Origin.SessionID)
  759. } else { // load
  760. for { // awaiting for saving session id
  761. d.Origin.SessionID = atomic.LoadUint64(&origin.SessionID)
  762. if d.Origin.SessionID != 0 {
  763. break
  764. }
  765. }
  766. d.Origin.SessionVersion = atomic.AddUint64(&origin.SessionVersion, 1)
  767. }
  768. }
  769. func isIceLiteSet(desc *sdp.SessionDescription) bool {
  770. for _, a := range desc.Attributes {
  771. if strings.TrimSpace(a.Key) == sdp.AttrKeyICELite {
  772. return true
  773. }
  774. }
  775. return false
  776. }
  777. func isExtMapAllowMixedSet(desc *sdp.SessionDescription) bool {
  778. for _, a := range desc.Attributes {
  779. if strings.TrimSpace(a.Key) == sdp.AttrKeyExtMapAllowMixed {
  780. return true
  781. }
  782. }
  783. return false
  784. }