sdp.go 23 KB

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