Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/selkies/ice/candidate.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

65 statements  

1# This Source Code Form is subject to the terms of the Mozilla Public 

2# License, v. 2.0. If a copy of the MPL was not distributed with this 

3# file, You can obtain one at https://mozilla.org/MPL/2.0/. 

4# 

5# This file incorporates work covered by the following copyright and 

6# permission notice: 

7# 

8# Copyright (c) 2018-2019 Jeremy Lainé. 

9# All rights reserved. 

10# 

11# Redistribution and use in source and binary forms, with or without 

12# modification, are permitted provided that the following conditions are met: 

13# 

14# * Redistributions of source code must retain the above copyright notice, 

15# this list of conditions and the following disclaimer. 

16# * Redistributions in binary form must reproduce the above copyright notice, 

17# this list of conditions and the following disclaimer in the documentation 

18# and/or other materials provided with the distribution. 

19# * Neither the name of aioice nor the names of its contributors may 

20# be used to endorse or promote products derived from this software without 

21# specific prior written permission. 

22# 

23# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 

24# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 

25# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 

26# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 

27# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 

28# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 

29# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 

30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 

31# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 

32# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

33 

34import hashlib 

35import ipaddress 

36from typing import Optional 

37 

38 

39def candidate_foundation( 

40 candidate_type: str, candidate_transport: str, base_address: str 

41) -> str: 

42 """ 

43 See RFC 5245 - 4.1.1.3. Computing Foundations 

44 """ 

45 key = "%s|%s|%s" % (candidate_type, candidate_transport, base_address) 

46 return hashlib.md5(key.encode("ascii"), usedforsecurity=False).hexdigest() 

47 

48 

49def candidate_priority( 

50 candidate_component: int, candidate_type: str, local_pref: int = 65535 

51) -> int: 

52 """ 

53 See RFC 5245 - 4.1.2.1. Recommended Formula 

54 """ 

55 if candidate_type == "host": 

56 type_pref = 126 

57 elif candidate_type == "prflx": 

58 type_pref = 110 

59 elif candidate_type == "srflx": 

60 type_pref = 100 

61 else: 

62 type_pref = 0 

63 

64 return (1 << 24) * type_pref + (1 << 8) * local_pref + (256 - candidate_component) 

65 

66 

67class Candidate: 

68 """ 

69 An ICE candidate. 

70 """ 

71 

72 def __init__( 

73 self, 

74 foundation: str, 

75 component: int, 

76 transport: str, 

77 priority: int, 

78 host: str, 

79 port: int, 

80 type: str, 

81 related_address: Optional[str] = None, 

82 related_port: Optional[int] = None, 

83 tcptype: Optional[str] = None, 

84 generation: Optional[int] = None, 

85 ) -> None: 

86 self.foundation = foundation 

87 self.component = component 

88 self.transport = transport 

89 self.priority = priority 

90 self.host = host 

91 self.port = port 

92 self.type = type 

93 self.related_address = related_address 

94 self.related_port = related_port 

95 self.tcptype = tcptype 

96 self.generation = generation 

97 

98 @classmethod 

99 def from_sdp(cls, sdp: str) -> "Candidate": 

100 """ 

101 Parse a :class:`Candidate` from SDP. 

102 

103 .. code-block:: python 

104 

105 Candidate.from_sdp( 

106 '6815297761 1 udp 659136 1.2.3.4 31102 typ host generation 0') 

107 """ 

108 bits = sdp.split() 

109 if len(bits) < 8: 

110 raise ValueError("SDP does not have enough properties") 

111 

112 related_address: Optional[str] = None 

113 related_port: Optional[int] = None 

114 tcptype: Optional[str] = None 

115 generation: Optional[int] = None 

116 for i in range(8, len(bits) - 1, 2): 

117 if bits[i] == "raddr": 

118 related_address = bits[i + 1] 

119 elif bits[i] == "rport": 

120 related_port = int(bits[i + 1]) 

121 elif bits[i] == "tcptype": 

122 tcptype = bits[i + 1] 

123 elif bits[i] == "generation": 

124 generation = int(bits[i + 1]) 

125 

126 return Candidate( 

127 foundation=bits[0], 

128 component=int(bits[1]), 

129 transport=bits[2], 

130 priority=int(bits[3]), 

131 host=bits[4], 

132 port=int(bits[5]), 

133 type=bits[7], 

134 related_address=related_address, 

135 related_port=related_port, 

136 tcptype=tcptype, 

137 generation=generation, 

138 ) 

139 

140 def to_sdp(self) -> str: 

141 """ 

142 Return a string representation suitable for SDP. 

143 """ 

144 sdp = "%s %d %s %d %s %d typ %s" % ( 

145 self.foundation, 

146 self.component, 

147 self.transport, 

148 self.priority, 

149 self.host, 

150 self.port, 

151 self.type, 

152 ) 

153 if self.related_address is not None: 

154 sdp += " raddr %s" % self.related_address 

155 if self.related_port is not None: 

156 sdp += " rport %s" % self.related_port 

157 if self.tcptype is not None: 

158 sdp += " tcptype %s" % self.tcptype 

159 if self.generation is not None: 

160 sdp += " generation %d" % self.generation 

161 return sdp 

162 

163 def can_pair_with(self, other: "Candidate") -> bool: 

164 """ 

165 A local candidate is paired with a remote candidate if and only if 

166 the two candidates have the same component ID and have the same IP 

167 address version. 

168 """ 

169 a = ipaddress.ip_address(self.host) 

170 b = ipaddress.ip_address(other.host) 

171 return ( 

172 self.component == other.component 

173 and self.transport.lower() == other.transport.lower() 

174 and a.version == b.version 

175 ) 

176 

177 def __repr__(self) -> str: 

178 return "Candidate(%s)" % self.to_sdp()