github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/sdp/sdp.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sdp
    16  
    17  import (
    18  	"strings"
    19  
    20  	"github.com/pion/sdp/v3"
    21  	"github.com/pion/webrtc/v3"
    22  )
    23  
    24  func GetMidValue(media *sdp.MediaDescription) string {
    25  	for _, attr := range media.Attributes {
    26  		if attr.Key == sdp.AttrKeyMID {
    27  			return attr.Value
    28  		}
    29  	}
    30  	return ""
    31  }
    32  
    33  func ExtractFingerprint(desc *sdp.SessionDescription) (string, string, error) {
    34  	fingerprints := make([]string, 0)
    35  
    36  	if fingerprint, haveFingerprint := desc.Attribute("fingerprint"); haveFingerprint {
    37  		fingerprints = append(fingerprints, fingerprint)
    38  	}
    39  
    40  	for _, m := range desc.MediaDescriptions {
    41  		if fingerprint, haveFingerprint := m.Attribute("fingerprint"); haveFingerprint {
    42  			fingerprints = append(fingerprints, fingerprint)
    43  		}
    44  	}
    45  
    46  	if len(fingerprints) < 1 {
    47  		return "", "", webrtc.ErrSessionDescriptionNoFingerprint
    48  	}
    49  
    50  	for _, m := range fingerprints {
    51  		if m != fingerprints[0] {
    52  			return "", "", webrtc.ErrSessionDescriptionConflictingFingerprints
    53  		}
    54  	}
    55  
    56  	parts := strings.Split(fingerprints[0], " ")
    57  	if len(parts) != 2 {
    58  		return "", "", webrtc.ErrSessionDescriptionInvalidFingerprint
    59  	}
    60  	return parts[1], parts[0], nil
    61  }
    62  
    63  func ExtractDTLSRole(desc *sdp.SessionDescription) webrtc.DTLSRole {
    64  	for _, md := range desc.MediaDescriptions {
    65  		setup, ok := md.Attribute(sdp.AttrKeyConnectionSetup)
    66  		if !ok {
    67  			continue
    68  		}
    69  
    70  		if setup == sdp.ConnectionRoleActive.String() {
    71  			return webrtc.DTLSRoleClient
    72  		}
    73  
    74  		if setup == sdp.ConnectionRolePassive.String() {
    75  			return webrtc.DTLSRoleServer
    76  		}
    77  	}
    78  
    79  	//
    80  	// If 'setup' attribute is not available, use client role
    81  	// as that is the default behaviour of answerers
    82  	//
    83  	// There seems to be some differences in how role is decided.
    84  	// libwebrtc (Chrome) code - (https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/pc/jsep_transport.cc;l=592;drc=369fb686729e7eb20d2bd09717cec14269a399d7)
    85  	// does not mention anything about ICE role when determining
    86  	// DTLS Role.
    87  	//
    88  	// But, ORTC has this - https://github.com/w3c/ortc/issues/167#issuecomment-69409953
    89  	// and pion/webrtc follows that (https://github.com/pion/webrtc/blob/e071a4eded1efd5d9b401bcfc4efacb3a2a5a53c/dtlstransport.go#L269)
    90  	//
    91  	// So if remote is ice-lite, pion will use DTLSRoleServer when answering
    92  	// while browsers pick DTLSRoleClient.
    93  	//
    94  	return webrtc.DTLSRoleClient
    95  }
    96  
    97  func ExtractICECredential(desc *sdp.SessionDescription) (string, string, error) {
    98  	remotePwds := []string{}
    99  	remoteUfrags := []string{}
   100  
   101  	if ufrag, haveUfrag := desc.Attribute("ice-ufrag"); haveUfrag {
   102  		remoteUfrags = append(remoteUfrags, ufrag)
   103  	}
   104  	if pwd, havePwd := desc.Attribute("ice-pwd"); havePwd {
   105  		remotePwds = append(remotePwds, pwd)
   106  	}
   107  
   108  	for _, m := range desc.MediaDescriptions {
   109  		if ufrag, haveUfrag := m.Attribute("ice-ufrag"); haveUfrag {
   110  			remoteUfrags = append(remoteUfrags, ufrag)
   111  		}
   112  		if pwd, havePwd := m.Attribute("ice-pwd"); havePwd {
   113  			remotePwds = append(remotePwds, pwd)
   114  		}
   115  	}
   116  
   117  	if len(remoteUfrags) == 0 {
   118  		return "", "", webrtc.ErrSessionDescriptionMissingIceUfrag
   119  	} else if len(remotePwds) == 0 {
   120  		return "", "", webrtc.ErrSessionDescriptionMissingIcePwd
   121  	}
   122  
   123  	for _, m := range remoteUfrags {
   124  		if m != remoteUfrags[0] {
   125  			return "", "", webrtc.ErrSessionDescriptionConflictingIceUfrag
   126  		}
   127  	}
   128  
   129  	for _, m := range remotePwds {
   130  		if m != remotePwds[0] {
   131  			return "", "", webrtc.ErrSessionDescriptionConflictingIcePwd
   132  		}
   133  	}
   134  
   135  	return remoteUfrags[0], remotePwds[0], nil
   136  }
   137  
   138  func ExtractStreamID(media *sdp.MediaDescription) (string, bool) {
   139  	var streamID string
   140  	msid, ok := media.Attribute(sdp.AttrKeyMsid)
   141  	if !ok {
   142  		return "", false
   143  	}
   144  	ids := strings.Split(msid, " ")
   145  	if len(ids) < 2 {
   146  		streamID = msid
   147  	} else {
   148  		streamID = ids[1]
   149  	}
   150  	return streamID, true
   151  }