sdp.go 24 KB

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