Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/selkies/webrtc/rtcdatachannel.py: 73%

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

105 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) 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 aiortc 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 logging 

35from dataclasses import dataclass 

36from typing import Optional, Union 

37 

38from pyee.asyncio import AsyncIOEventEmitter 

39 

40from .exceptions import InvalidStateError 

41 

42logger = logging.getLogger(__name__) 

43 

44 

45@dataclass 

46class RTCDataChannelParameters: 

47 """ 

48 The :class:`RTCDataChannelParameters` dictionary describes the 

49 configuration of an :class:`RTCDataChannel`. 

50 """ 

51 

52 label: str = "" 

53 "A name describing the data channel." 

54 

55 maxPacketLifeTime: Optional[int] = None 

56 "The maximum time in milliseconds during which transmissions are attempted." 

57 

58 maxRetransmits: Optional[int] = None 

59 "The maximum number of retransmissions that are attempted." 

60 

61 ordered: bool = True 

62 "Whether the data channel guarantees in-order delivery of messages." 

63 

64 protocol: str = "" 

65 "The name of the subprotocol in use." 

66 

67 negotiated: bool = False 

68 """ 

69 Whether data channel will be negotiated out of-band, where both sides 

70 create data channel with an agreed-upon ID.""" 

71 

72 id: Optional[int] = None 

73 """ 

74 An numeric ID for the channel; permitted values are 0-65534. 

75 If you don't include this option, the user agent will select an ID for you. 

76 Must be set when negotiating out-of-band. 

77 """ 

78 

79 

80class RTCDataChannel(AsyncIOEventEmitter): 

81 """ 

82 The :class:`RTCDataChannel` interface represents a network channel which 

83 can be used for bidirectional peer-to-peer transfers of arbitrary data. 

84 

85 :param transport: An :class:`RTCSctpTransport`. 

86 :param parameters: An :class:`RTCDataChannelParameters`. 

87 """ 

88 

89 def __init__( 

90 self, 

91 transport: "RTCSctpTransport", 

92 parameters: RTCDataChannelParameters, 

93 send_open: bool = True, 

94 ) -> None: 

95 super().__init__() 

96 self.__bufferedAmount = 0 

97 self.__bufferedAmountLowThreshold = 0 

98 self.__id = parameters.id 

99 self.__parameters = parameters 

100 self.__readyState = "connecting" 

101 self.__transport = transport 

102 self.__send_open = send_open 

103 

104 if self.__parameters.negotiated and ( 

105 self.__id is None or self.__id < 0 or self.__id > 65534 

106 ): 

107 raise ValueError( 

108 "ID must be in range 0-65534 if data channel is negotiated out-of-band" 

109 ) 

110 

111 if not self.__parameters.negotiated: 

112 if self.__send_open: 

113 self.__send_open = False 

114 self.__transport._data_channel_open(self) 

115 else: 

116 self.__transport._data_channel_add_negotiated(self) 

117 

118 @property 

119 def bufferedAmount(self) -> int: 

120 """ 

121 The number of bytes of data currently queued to be sent over the data channel. 

122 """ 

123 return self.__bufferedAmount 

124 

125 @property 

126 def bufferedAmountLowThreshold(self) -> int: 

127 """ 

128 The number of bytes of buffered outgoing data that is considered "low". 

129 """ 

130 return self.__bufferedAmountLowThreshold 

131 

132 @bufferedAmountLowThreshold.setter 

133 def bufferedAmountLowThreshold(self, value: int) -> None: 

134 if value < 0 or value > 4294967295: 

135 raise ValueError( 

136 "bufferedAmountLowThreshold must be in range 0 - 4294967295" 

137 ) 

138 self.__bufferedAmountLowThreshold = value 

139 

140 @property 

141 def negotiated(self) -> bool: 

142 """ 

143 Whether data channel was negotiated out-of-band. 

144 """ 

145 return self.__parameters.negotiated 

146 

147 @property 

148 def id(self) -> Optional[int]: 

149 """ 

150 An ID number which uniquely identifies the data channel. 

151 """ 

152 return self.__id 

153 

154 @property 

155 def label(self) -> str: 

156 """ 

157 A name describing the data channel. 

158 

159 These labels are not required to be unique. 

160 """ 

161 return self.__parameters.label 

162 

163 @property 

164 def ordered(self) -> bool: 

165 """ 

166 Indicates whether or not the data channel guarantees in-order delivery of 

167 messages. 

168 """ 

169 return self.__parameters.ordered 

170 

171 @property 

172 def maxPacketLifeTime(self) -> Optional[int]: 

173 """ 

174 The maximum time in milliseconds during which transmissions are attempted. 

175 """ 

176 return self.__parameters.maxPacketLifeTime 

177 

178 @property 

179 def maxRetransmits(self) -> Optional[int]: 

180 """ 

181 "The maximum number of retransmissions that are attempted. 

182 """ 

183 return self.__parameters.maxRetransmits 

184 

185 @property 

186 def protocol(self) -> str: 

187 """ 

188 The name of the subprotocol in use. 

189 """ 

190 return self.__parameters.protocol 

191 

192 @property 

193 def readyState(self) -> str: 

194 """ 

195 A string indicating the current state of the underlying data transport. 

196 """ 

197 return self.__readyState 

198 

199 @property 

200 def transport(self) -> "RTCSctpTransport": 

201 """ 

202 The :class:`RTCSctpTransport` over which data is transmitted. 

203 """ 

204 return self.__transport 

205 

206 def close(self) -> None: 

207 """ 

208 Close the data channel. 

209 """ 

210 self.transport._data_channel_close(self) 

211 

212 def send(self, data: Union[bytes, str]) -> None: 

213 """ 

214 Send `data` across the data channel to the remote peer. 

215 """ 

216 if self.readyState != "open": 

217 raise InvalidStateError 

218 

219 if not isinstance(data, (str, bytes)): 

220 raise ValueError(f"Cannot send unsupported data type: {type(data)}") 

221 

222 self.transport._data_channel_send(self, data) 

223 

224 def _addBufferedAmount(self, amount: int) -> None: 

225 crosses_threshold = ( 

226 self.__bufferedAmount > self.bufferedAmountLowThreshold 

227 and self.__bufferedAmount + amount <= self.bufferedAmountLowThreshold 

228 ) 

229 self.__bufferedAmount += amount 

230 if crosses_threshold: 

231 self.emit("bufferedamountlow") 

232 

233 def _setId(self, id: int) -> None: 

234 self.__id = id 

235 

236 def _setReadyState(self, state: str) -> None: 

237 if state != self.__readyState: 

238 self.__log_debug("- %s -> %s", self.__readyState, state) 

239 self.__readyState = state 

240 

241 if state == "open": 

242 self.emit("open") 

243 elif state == "closed": 

244 self.emit("close") 

245 

246 # no more events will be emitted, so remove all event listeners 

247 # to facilitate garbage collection. 

248 self.remove_all_listeners() 

249 

250 def __log_debug(self, msg: str, *args: object) -> None: 

251 logger.debug(f"RTCDataChannel(%s) {msg}", self.__id, *args) 

252 

253 

254from .rtcsctptransport import RTCSctpTransport # noqa