Skip to content

Commit edf07bb

Browse files
committed
Add PeerConneciton.set_packet_loss
1 parent 2ef1582 commit edf07bb

File tree

3 files changed

+105
-10
lines changed

3 files changed

+105
-10
lines changed

lib/ex_webrtc/dtls_transport.ex

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ defmodule ExWebRTC.DTLSTransport do
9696
GenServer.cast(dtls_transport, {:send_data, data})
9797
end
9898

99+
@spec set_packet_loss(dtls_transport(), 0..100) :: :ok
100+
def set_packet_loss(dtls_transport, packet_loss) do
101+
GenServer.cast(dtls_transport, {:set_packet_loss, packet_loss})
102+
end
103+
99104
@spec stop(dtls_transport()) :: :ok
100105
def stop(dtls_transport) do
101106
GenServer.stop(dtls_transport)
@@ -127,7 +132,8 @@ defmodule ExWebRTC.DTLSTransport do
127132
peer_fingerprint: nil,
128133
dtls_state: :new,
129134
dtls: nil,
130-
mode: nil
135+
mode: nil,
136+
packet_loss: 0
131137
}
132138

133139
notify(state.owner, {:state_change, :new})
@@ -142,7 +148,7 @@ defmodule ExWebRTC.DTLSTransport do
142148
if state.mode == :active do
143149
{packets, timeout} = ExDTLS.do_handshake(state.dtls)
144150
Process.send_after(self(), :dtls_timeout, timeout)
145-
:ok = state.ice_transport.send_data(state.ice_pid, packets)
151+
:ok = do_send(state, packets)
146152
state = update_dtls_state(state, :connecting)
147153
Logger.debug("Started DTLS handshake")
148154
{:reply, :ok, state}
@@ -157,7 +163,7 @@ defmodule ExWebRTC.DTLSTransport do
157163

158164
if state.buffered_local_packets do
159165
Logger.debug("Sending buffered DTLS packets")
160-
:ok = state.ice_transport.send_data(state.ice_pid, state.buffered_local_packets)
166+
:ok = do_send(state, state.buffered_local_packets)
161167
state = %{state | buffered_local_packets: nil}
162168
{:reply, :ok, state}
163169
else
@@ -236,7 +242,7 @@ defmodule ExWebRTC.DTLSTransport do
236242
@impl true
237243
def handle_cast({:send_rtp, data}, %{dtls_state: :connected, ice_connected: true} = state) do
238244
case ExLibSRTP.protect(state.out_srtp, data) do
239-
{:ok, protected} -> state.ice_transport.send_data(state.ice_pid, protected)
245+
{:ok, protected} -> do_send(state, protected)
240246
{:error, reason} -> Logger.warning("Unable to protect RTP: #{inspect(reason)}")
241247
end
242248

@@ -252,7 +258,7 @@ defmodule ExWebRTC.DTLSTransport do
252258
@impl true
253259
def handle_cast({:send_rtcp, data}, state) do
254260
case ExLibSRTP.protect_rtcp(state.out_srtp, data) do
255-
{:ok, protected} -> state.ice_transport.send_data(state.ice_pid, protected)
261+
{:ok, protected} -> do_send(state, protected)
256262
{:error, reason} -> Logger.warning("Unable to protect RTCP: #{inspect(reason)}")
257263
end
258264

@@ -262,18 +268,24 @@ defmodule ExWebRTC.DTLSTransport do
262268
@impl true
263269
def handle_cast({:send_data, data}, state) do
264270
case ExDTLS.write_data(state.dtls, data) do
265-
{:ok, protected} -> state.ice_transport.send_data(state.ice_pid, protected)
271+
{:ok, protected} -> do_send(state, protected)
266272
{:error, reason} -> Logger.warning("Unable to protect data: #{inspect(reason)}")
267273
end
268274

269275
{:noreply, state}
270276
end
271277

278+
@impl true
279+
def handle_cast({:set_packet_loss, value}, state) do
280+
state = %{state | packet_loss: value}
281+
{:noreply, state}
282+
end
283+
272284
@impl true
273285
def handle_info(:dtls_timeout, %{buffered_local_packets: buffered_local_packets} = state) do
274286
case ExDTLS.handle_timeout(state.dtls) do
275287
{:retransmit, packets, timeout} when state.ice_connected ->
276-
state.ice_transport.send_data(state.ice_pid, packets)
288+
do_send(state, packets)
277289
Logger.debug("Retransmitted DTLS packets")
278290
Process.send_after(self(), :dtls_timeout, timeout)
279291

@@ -327,7 +339,7 @@ defmodule ExWebRTC.DTLSTransport do
327339
defp handle_ice_data({:data, <<f, _rest::binary>> = data}, state) when f in 20..63 do
328340
case ExDTLS.handle_data(state.dtls, data) do
329341
{:handshake_packets, packets, timeout} when state.ice_connected ->
330-
:ok = state.ice_transport.send_data(state.ice_pid, packets)
342+
:ok = do_send(state, packets)
331343
Process.send_after(self(), :dtls_timeout, timeout)
332344
state = update_dtls_state(state, :connecting)
333345
{:ok, state}
@@ -346,7 +358,7 @@ defmodule ExWebRTC.DTLSTransport do
346358
{:handshake_finished, lkm, rkm, profile, packets} ->
347359
Logger.debug("DTLS handshake finished")
348360
state = update_remote_cert_info(state)
349-
state.ice_transport.send_data(state.ice_pid, packets)
361+
do_send(state, packets)
350362

351363
peer_fingerprint =
352364
state.dtls
@@ -466,5 +478,16 @@ defmodule ExWebRTC.DTLSTransport do
466478
%{state | buffered_remote_rtp_packets: []}
467479
end
468480

481+
defp do_send(%{packet_loss: 0} = state, data),
482+
do: state.ice_transport.send_data(state.ice_pid, data)
483+
484+
defp do_send(state, data) do
485+
if Enum.random(1..100) > state.packet_loss do
486+
state.ice_transport.send_data(state.ice_pid, data)
487+
else
488+
:ok
489+
end
490+
end
491+
469492
defp notify(dst, msg), do: send(dst, {:dtls_transport, self(), msg})
470493
end

lib/ex_webrtc/peer_connection.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,16 @@ defmodule ExWebRTC.PeerConnection do
181181
GenServer.cast(peer_connection, {:send_data, channel_ref, data})
182182
end
183183

184+
@doc """
185+
Sets very simple packet loss.
186+
187+
Can be used for experimental purposes.
188+
"""
189+
@spec set_packet_loss(peer_connection(), 0..100) :: :ok
190+
def set_packet_loss(peer_connection, value) when value in 0..100 do
191+
GenServer.cast(peer_connection, {:set_packet_loss, value})
192+
end
193+
184194
#### MDN-API ####
185195

186196
@doc """
@@ -1162,6 +1172,12 @@ defmodule ExWebRTC.PeerConnection do
11621172
{:noreply, %{state | sctp_transport: sctp_transport}}
11631173
end
11641174

1175+
@impl true
1176+
def handle_cast({:set_packet_loss, packet_loss}, state) do
1177+
DTLSTransport.set_packet_loss(state.dtls_transport, packet_loss)
1178+
{:noreply, state}
1179+
end
1180+
11651181
@impl true
11661182
def handle_info({:ex_ice, _from, {:connection_state_change, new_ice_state}}, state) do
11671183
state = %{state | ice_state: new_ice_state}

test/ex_webrtc/dtls_transport_test.exs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ defmodule ExWebRTC.DTLSTransportTest do
99
|> ExDTLS.get_cert_fingerprint()
1010
|> Utils.hex_dump()
1111

12+
@rtp_header <<1::1, 0::1, 0::1, 0::1, 0::4, 0::1, 96::7, 1::16, 1::32, 1::32>>
13+
@rtp_payload <<0>>
14+
@rtp_packet <<@rtp_header::binary, @rtp_payload::binary>>
15+
16+
# empty rr packet
17+
@rtcp_rr_header <<2::2, 0::1, 0::5, 201::8, 1::16, 1::32>>
18+
@rtcp_rr_packet <<@rtcp_rr_header::binary>>
19+
1220
defmodule MockICETransport do
1321
@behaviour ExWebRTC.ICETransport
1422

@@ -87,7 +95,7 @@ defmodule ExWebRTC.DTLSTransportTest do
8795
end
8896

8997
test "cannot send data when handshake not finished", %{dtls: dtls} do
90-
DTLSTransport.send_rtp(dtls, <<1, 2, 3>>)
98+
DTLSTransport.send_rtp(dtls, @rtp_packet)
9199

92100
refute_receive {:mock_ice, _data}
93101
end
@@ -175,6 +183,14 @@ defmodule ExWebRTC.DTLSTransportTest do
175183
assert :ok = check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
176184
assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}}
177185
assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}}
186+
187+
# assert we can send data
188+
assert :ok = DTLSTransport.send_rtp(dtls, @rtp_packet)
189+
assert_receive {:mock_ice, <<@rtp_header::binary, _payload::binary>>}
190+
assert :ok = DTLSTransport.send_rtcp(dtls, @rtcp_rr_packet)
191+
assert_receive {:mock_ice, <<@rtcp_rr_header::binary, _payload::binary>>}
192+
assert :ok = DTLSTransport.send_data(dtls, <<1, 2, 3>>)
193+
assert_receive {:mock_ice, _datachannel_packet}
178194
end
179195

180196
test "finishes handshake in passive mode", %{
@@ -200,6 +216,46 @@ defmodule ExWebRTC.DTLSTransportTest do
200216
assert :ok == check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
201217
assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}}
202218
assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}}
219+
220+
# assert we can send data
221+
assert :ok = DTLSTransport.send_rtp(dtls, @rtp_packet)
222+
assert_receive {:mock_ice, <<@rtp_header::binary, _payload::binary>>}
223+
assert :ok = DTLSTransport.send_rtcp(dtls, @rtcp_rr_packet)
224+
assert_receive {:mock_ice, <<@rtcp_rr_header::binary, _payload::binary>>}
225+
assert :ok = DTLSTransport.send_data(dtls, <<1, 2, 3>>)
226+
assert_receive {:mock_ice, _datachannel_packet}
227+
end
228+
229+
test "drops packets when packet loss is set", %{
230+
dtls: dtls,
231+
ice_transport: ice_transport,
232+
ice_pid: ice_pid
233+
} do
234+
:ok = DTLSTransport.start_dtls(dtls, :active, @fingerprint)
235+
remote_dtls = ExDTLS.init(mode: :server, dtls_srtp: true)
236+
237+
:ok = DTLSTransport.set_ice_connected(dtls)
238+
239+
assert :ok = check_handshake(dtls, ice_transport, ice_pid, remote_dtls)
240+
assert_receive {:dtls_transport, ^dtls, {:state_change, :connecting}}
241+
assert_receive {:dtls_transport, ^dtls, {:state_change, :connected}}
242+
243+
# assert we can send data
244+
DTLSTransport.send_rtp(dtls, @rtp_packet)
245+
assert_receive {:mock_ice, <<@rtp_header::binary, _payload::binary>>}
246+
DTLSTransport.send_rtcp(dtls, @rtcp_rr_packet)
247+
assert_receive {:mock_ice, <<@rtcp_rr_packet::binary, _rest::binary>>}
248+
DTLSTransport.send_data(dtls, <<1, 2, 3>>)
249+
assert_receive {:mock_ice, _datachannel_packet}
250+
251+
# now set packet-loss
252+
DTLSTransport.set_packet_loss(dtls, 100)
253+
DTLSTransport.send_rtp(dtls, @rtp_packet)
254+
refute_receive {:mock_ice, _rtp_packet}
255+
DTLSTransport.send_rtcp(dtls, @rtcp_rr_packet)
256+
refute_receive {:mock_ice, _rtcp_rr_packet}
257+
DTLSTransport.send_data(dtls, <<1, 2, 3>>)
258+
refute_receive {:mock_ice, _datachannel_packet}
203259
end
204260

205261
defp check_handshake(dtls, ice_transport, ice_pid, remote_dtls) do

0 commit comments

Comments
 (0)