sdp.go 23 KB

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