Skip to content

Commit dfcfa8b

Browse files
committed
Add get_stats
1 parent 7f85ac4 commit dfcfa8b

9 files changed

+279
-23
lines changed

lib/ex_webrtc/app.ex

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule ExWebRTC.App do
2+
use Application
3+
4+
def start(_type, _args) do
5+
IO.inspect(:loading_ex_webrtc_app)
6+
children = [{Registry, keys: :unique, name: ExWebRTC.Registry}]
7+
Supervisor.start_link(children, strategy: :one_for_one)
8+
end
9+
end

lib/ex_webrtc/dtls_transport.ex

+51
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ defmodule ExWebRTC.DTLSTransport do
4949
GenServer.call(dtls_transport, :set_ice_connected)
5050
end
5151

52+
@spec get_local_cert_info(dtls_transport()) :: map()
53+
def get_local_cert_info(dtls_transport) do
54+
GenServer.call(dtls_transport, :get_local_cert_info)
55+
end
56+
57+
@spec get_remote_cert_info(dtls_transport()) :: map() | nil
58+
def get_remote_cert_info(dtls_transport) do
59+
GenServer.call(dtls_transport, :get_remote_cert_info)
60+
end
61+
5262
@spec get_fingerprint(dtls_transport()) :: binary()
5363
def get_fingerprint(dtls_transport) do
5464
GenServer.call(dtls_transport, :get_fingerprint)
@@ -87,8 +97,12 @@ defmodule ExWebRTC.DTLSTransport do
8797
ice_connected: false,
8898
buffered_packets: nil,
8999
cert: cert,
100+
base64_cert: Base.encode64(cert),
90101
pkey: pkey,
91102
fingerprint: fingerprint,
103+
remote_cert: nil,
104+
remote_base64_cert: nil,
105+
remote_fingerprint: nil,
92106
in_srtp: ExLibSRTP.new(),
93107
out_srtp: ExLibSRTP.new(),
94108
# sha256 hex dump
@@ -133,6 +147,33 @@ defmodule ExWebRTC.DTLSTransport do
133147
end
134148
end
135149

150+
@impl true
151+
def handle_call(:get_local_cert_info, _from, state) do
152+
cert_info = %{
153+
fingerprint: state.fingerprint,
154+
fingerprint_algorithm: :sha_256,
155+
base64_certificate: state.base64_cert
156+
}
157+
158+
{:reply, cert_info, state}
159+
end
160+
161+
@impl true
162+
def handle_call(:get_remote_cert_info, _from, %{dtls_state: :connected} = state) do
163+
cert_info = %{
164+
fingerprint: state.remote_fingerprint,
165+
fingerprint_algorithm: :sha_256,
166+
base64_certificate: state.remote_base64_cert
167+
}
168+
169+
{:reply, cert_info, state}
170+
end
171+
172+
@impl true
173+
def handle_call(:get_remote_cert_info, _from, state) do
174+
{:reply, nil, state}
175+
end
176+
136177
@impl true
137178
def handle_call(:get_fingerprint, _from, state) do
138179
{:reply, state.fingerprint, state}
@@ -247,6 +288,7 @@ defmodule ExWebRTC.DTLSTransport do
247288

248289
{:handshake_finished, lkm, rkm, profile, packets} ->
249290
Logger.debug("DTLS handshake finished")
291+
state = update_remote_cert_info(state)
250292
state.ice_transport.send_data(state.ice_pid, packets)
251293

252294
peer_fingerprint =
@@ -269,6 +311,7 @@ defmodule ExWebRTC.DTLSTransport do
269311
Logger.debug("DTLS handshake finished")
270312
:ok = setup_srtp(state, lkm, rkm, profile)
271313
state = update_dtls_state(state, :connected)
314+
state = update_remote_cert_info(state)
272315
{:ok, state}
273316

274317
:handshake_want_read ->
@@ -340,5 +383,13 @@ defmodule ExWebRTC.DTLSTransport do
340383
%{state | dtls_state: new_dtls_state}
341384
end
342385

386+
defp update_remote_cert_info(state) do
387+
cert = ExDTLS.get_cert(state.dtls)
388+
fingerprint = ExDTLS.get_cert_fingerprint(cert)
389+
base64_cert = Base.encode64(cert)
390+
391+
%{state | remote_cert: cert, remote_base64_cert: base64_cert, remote_fingerprint: fingerprint}
392+
end
393+
343394
defp notify(dst, msg), do: send(dst, {:dtls_transport, self(), msg})
344395
end

lib/ex_webrtc/ice_transport.ex

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ defmodule ExWebRTC.ICETransport do
1414
@callback restart(pid()) :: :ok
1515
@callback send_data(pid(), binary()) :: :ok
1616
@callback set_remote_credentials(pid(), ufrag :: binary(), pwd :: binary()) :: :ok
17+
@callback get_stats(pid()) :: map()
1718
@callback stop(pid()) :: :ok
1819
end
1920

@@ -43,5 +44,7 @@ defmodule ExWebRTC.DefaultICETransport do
4344
@impl true
4445
defdelegate set_remote_credentials(pid, ufrag, pwd), to: ICEAgent
4546
@impl true
47+
defdelegate get_stats(pid), to: ICEAgent
48+
@impl true
4649
defdelegate stop(pid), to: ICEAgent
4750
end

lib/ex_webrtc/peer_connection.ex

+138-12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule ExWebRTC.PeerConnection do
1818
MediaStreamTrack,
1919
RTPTransceiver,
2020
RTPSender,
21+
RTPReceiver,
2122
SDPUtils,
2223
SessionDescription,
2324
Utils
@@ -59,6 +60,11 @@ defmodule ExWebRTC.PeerConnection do
5960
@type connection_state() :: :closed | :failed | :disconnected | :new | :connecting | :connected
6061

6162
#### API ####
63+
@spec get_all_peer_connections() :: [pid()]
64+
def get_all_peer_connections() do
65+
Registry.select(ExWebRTC.Registry, [{{:_, :"$1", :_}, [], [:"$1"]}])
66+
end
67+
6268
@spec start_link(Configuration.options()) :: GenServer.on_start()
6369
def start_link(options \\ []) do
6470
configuration = Configuration.from_options!(options)
@@ -157,6 +163,11 @@ defmodule ExWebRTC.PeerConnection do
157163
GenServer.call(peer_connection, {:remove_track, sender_id})
158164
end
159165

166+
@spec get_stats(peer_connection()) :: %{String.t() => term()}
167+
def get_stats(peer_connection) do
168+
GenServer.call(peer_connection, :get_stats)
169+
end
170+
160171
@spec send_rtp(peer_connection(), String.t(), ExRTP.Packet.t()) :: :ok
161172
def send_rtp(peer_connection, track_id, packet) do
162173
GenServer.cast(peer_connection, {:send_rtp, track_id, packet})
@@ -171,6 +182,7 @@ defmodule ExWebRTC.PeerConnection do
171182

172183
@impl true
173184
def init({owner, config}) do
185+
{:ok, _} = Registry.register(ExWebRTC.Registry, self(), self())
174186
ice_config = [stun_servers: config.ice_servers, ip_filter: config.ice_ip_filter, on_data: nil]
175187
{:ok, ice_pid} = DefaultICETransport.start_link(:controlled, ice_config)
176188
{:ok, dtls_transport} = DTLSTransport.start_link(DefaultICETransport, ice_pid)
@@ -519,7 +531,7 @@ defmodule ExWebRTC.PeerConnection do
519531
{:reply, :ok, state}
520532

521533
true ->
522-
# that's not compliant with the W3C but it's safer not
534+
# that's not compliant with the W3C but it's safer not
523535
# to allow for this until we have clear use case
524536
{:reply, {:error, :invalid_transceiver_direction}, state}
525537
end
@@ -558,6 +570,118 @@ defmodule ExWebRTC.PeerConnection do
558570
end
559571
end
560572

573+
@impl true
574+
def handle_call(:get_stats, _from, state) do
575+
timestamp = System.os_time(:millisecond)
576+
577+
ice_stats = state.ice_transport.get_stats(state.ice_pid)
578+
local_cert_info = DTLSTransport.get_local_cert_info(state.dtls_transport)
579+
remote_cert_info = DTLSTransport.get_remote_cert_info(state.dtls_transport)
580+
581+
remote_certificate =
582+
if remote_cert_info != nil do
583+
%{
584+
id: :remote_certificate,
585+
type: :certificate,
586+
timestamp: timestamp,
587+
fingerprint: Utils.hex_dump(remote_cert_info.fingerprint),
588+
fingerprint_algorithm: remote_cert_info.fingerprint_algorithm,
589+
base64_certificate: remote_cert_info.base64_certificate
590+
}
591+
else
592+
%{
593+
id: :remote_certificate,
594+
type: :certificate,
595+
timestamp: timestamp,
596+
fingerprint: nil,
597+
fingerprint_algorithm: nil,
598+
base64_certificate: nil
599+
}
600+
end
601+
602+
local_cands =
603+
Map.new(ice_stats.local_candidates, fn local_cand ->
604+
cand = %{
605+
id: local_cand.id,
606+
timestamp: timestamp,
607+
type: :local_candidate,
608+
address: local_cand.address,
609+
port: local_cand.port,
610+
protocol: local_cand.transport,
611+
candidate_type: local_cand.type,
612+
priority: local_cand.priority,
613+
foundation: local_cand.foundation,
614+
related_address: local_cand.base_address,
615+
related_port: local_cand.base_port
616+
}
617+
618+
{cand.id, cand}
619+
end)
620+
621+
rtp_stats =
622+
Enum.flat_map(state.transceivers, fn tr ->
623+
case tr.current_direction do
624+
:sendonly ->
625+
[RTPSender.get_stats(tr.sender, timestamp)]
626+
627+
:recvonly ->
628+
[RTPReceiver.get_stats(tr.receiver, timestamp)]
629+
630+
:sendrecv ->
631+
[
632+
RTPSender.get_stats(tr.sender, timestamp),
633+
RTPReceiver.get_stats(tr.receiver, timestamp)
634+
]
635+
636+
_other ->
637+
[]
638+
end
639+
end)
640+
|> Map.new(fn stats -> {stats.id, stats} end)
641+
642+
stats = %{
643+
peer_connection: %{
644+
id: :peer_connection,
645+
type: :peer_connection,
646+
timestamp: timestamp,
647+
signaling_state: state.signaling_state,
648+
ice_state: state.ice_state,
649+
ice_gathering_state: state.ice_gathering_state,
650+
dtls_state: state.dtls_state,
651+
negotiation_needed: state.negotiation_needed,
652+
connection_state: state.conn_state
653+
},
654+
transport: %{
655+
id: :transport,
656+
type: :transport,
657+
timestamp: timestamp,
658+
bytes_sent: ice_stats.bytes_sent,
659+
bytes_received: ice_stats.bytes_received,
660+
packets_sent: ice_stats.packets_sent,
661+
packets_received: ice_stats.packets_received,
662+
ice_role: ice_stats.role,
663+
ice_local_ufrag: ice_stats.local_ufrag,
664+
ice_state: ice_stats.state
665+
},
666+
local_certificate: %{
667+
id: :local_certificate,
668+
type: :certificate,
669+
timestamp: timestamp,
670+
fingerprint: Utils.hex_dump(local_cert_info.fingerprint),
671+
fingerprint_algorithm: local_cert_info.fingerprint_algorithm,
672+
base64_certificate: local_cert_info.base64_certificate
673+
},
674+
remote_certificate: remote_certificate
675+
}
676+
677+
stats =
678+
stats
679+
|> Map.merge(local_cands)
680+
|> Map.merge(rtp_stats)
681+
682+
{:reply, stats, state}
683+
end
684+
561685
@impl true
562686
def handle_cast({:send_rtp, track_id, packet}, state) do
563687
# TODO: iterating over transceivers is not optimal
@@ -638,10 +762,12 @@ defmodule ExWebRTC.PeerConnection do
638762
@impl true
639763
def handle_info({:dtls_transport, _pid, {:rtp, data}}, state) do
640764
with {:ok, demuxer, mid, packet} <- Demuxer.demux(state.demuxer, data),
641-
%RTPTransceiver{} = t <- Enum.find(state.transceivers, &(&1.mid == mid)) do
642-
track_id = t.receiver.track.id
643-
notify(state.owner, {:rtp, track_id, packet})
644-
{:noreply, %{state | demuxer: demuxer}}
765+
{idx, %RTPTransceiver{} = t} <- find_transceiver(state.transceivers, mid) do
766+
receiver = RTPReceiver.receive(t.receiver, packet, data)
767+
transceivers = List.update_at(state.transceivers, idx, &%{&1 | receiver: receiver})
768+
state = %{state | demuxer: demuxer, transceivers: transceivers}
769+
notify(state.owner, {:rtp, t.receiver.track.id, packet})
770+
{:noreply, state}
645771
else
646772
nil ->
647773
Logger.warning("Received RTP with unrecognized MID: #{inspect(data)}")
@@ -717,7 +843,7 @@ defmodule ExWebRTC.PeerConnection do
717843
# mline from the last offer/answer, do it (i.e. recycle free mline)
718844
# * If there is no transceiver's mline, just rewrite
719845
# mline from the offer/answer respecting its port number i.e. whether
720-
# it is rejected or not.
846+
# it is rejected or not.
721847
# This is to preserve the same number of mlines
722848
# between subsequent offer/anser exchanges.
723849
# * At the end, add remaining transceiver mlines
@@ -751,7 +877,7 @@ defmodule ExWebRTC.PeerConnection do
751877
end
752878

753879
# next_mline_idx is future mline idx to use if there are no mlines to recycle
754-
# next_mid is the next free mid
880+
# next_mid is the next free mid
755881
defp assign_mlines(
756882
transceivers,
757883
last_answer,
@@ -1154,7 +1280,7 @@ defmodule ExWebRTC.PeerConnection do
11541280

11551281
# If signaling state is not stable i.e. we are during negotiation,
11561282
# don't fire negotiation needed notification.
1157-
# We will do this when moving to the stable state as part of the
1283+
# We will do this when moving to the stable state as part of the
11581284
# steps for setting remote description.
11591285
defp update_negotiation_needed(%{signaling_state: sig_state} = state) when sig_state != :stable,
11601286
do: state
@@ -1172,14 +1298,14 @@ defmodule ExWebRTC.PeerConnection do
11721298

11731299
negotiation_needed == false ->
11741300
# We need to clear the flag.
1175-
# Consider scenario where we add a transceiver and then
1176-
# remove it without performing negotiation.
1301+
# Consider scenario where we add a transceiver and then
1302+
# remove it without performing negotiation.
11771303
# At the end of the day, negotiation_needed flag has to be cleared.
11781304
%{state | negotiation_needed: false}
11791305
end
11801306
end
11811307

1182-
# We don't support MSIDs and stopping transceivers so
1308+
# We don't support MSIDs and stopping transceivers so
11831309
# we only check 5.2 and 5.3 from 4.7.3#check-if-negotiation-is-needed
11841310
# https://www.w3.org/TR/webrtc/#dfn-check-if-negotiation-is-needed
11851311
defp negotiation_needed?([], _), do: false
@@ -1199,7 +1325,7 @@ defmodule ExWebRTC.PeerConnection do
11991325
cond do
12001326
# Consider the following scenario:
12011327
# 1. offerer offers sendrecv
1202-
# 2. answerer answers with recvonly
1328+
# 2. answerer answers with recvonly
12031329
# 3. offerer changes from sendrecv to sendonly
12041330
# We don't need to renegotiate in such a case.
12051331
local_desc_type == :offer and

lib/ex_webrtc/rtp_receiver.ex

+28-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,34 @@ defmodule ExWebRTC.RTPReceiver do
66
alias ExWebRTC.MediaStreamTrack
77

88
@type t() :: %__MODULE__{
9-
track: MediaStreamTrack.t() | nil
9+
track: MediaStreamTrack.t(),
10+
ssrc: non_neg_integer() | nil,
11+
bytes_received: non_neg_integer(),
12+
packets_received: non_neg_integer()
1013
}
1114

12-
defstruct [:track]
15+
defstruct [:track, :ssrc, bytes_received: 0, packets_received: 0]
16+
17+
@doc false
18+
def receive(receiver, packet, raw_packet) do
19+
%__MODULE__{
20+
receiver
21+
| ssrc: packet.ssrc,
22+
bytes_received: receiver.bytes_received + byte_size(raw_packet),
23+
packets_received: receiver.packets_received + 1
24+
}
25+
end
26+
27+
@doc false
28+
@spec get_stats(t(), non_neg_integer()) :: map()
29+
def get_stats(receiver, timestamp) do
30+
%{
31+
id: receiver.track.id,
32+
type: :inbound_rtp,
33+
timestamp: timestamp,
34+
ssrc: receiver.ssrc,
35+
bytes_received: receiver.bytes_received,
36+
packets_received: receiver.packets_received
37+
}
38+
end
1339
end

0 commit comments

Comments
 (0)