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