1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/peerconnection/media_stream_track_metrics.h"
6
7 #include <inttypes.h>
8 #include <string>
9
10 //#include "base/hash/md5.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/threading/thread_checker.h"
13 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
14 #include "third_party/blink/public/platform/platform.h"
15
16 namespace blink {
17
18 class MediaStreamTrackMetricsObserver {
19 public:
20 MediaStreamTrackMetricsObserver(MediaStreamTrackMetrics::Direction direction,
21 MediaStreamTrackMetrics::Kind kind,
22 std::string track_id,
23 MediaStreamTrackMetrics* owner);
24 ~MediaStreamTrackMetricsObserver();
25
26 // Sends begin/end messages for the track if not already reported.
27 void SendLifetimeMessageForTrack(
28 MediaStreamTrackMetrics::LifetimeEvent event);
29
direction()30 MediaStreamTrackMetrics::Direction direction() {
31 DCHECK(thread_checker_.CalledOnValidThread());
32 return direction_;
33 }
34
kind()35 MediaStreamTrackMetrics::Kind kind() {
36 DCHECK(thread_checker_.CalledOnValidThread());
37 return kind_;
38 }
39
track_id() const40 std::string track_id() const {
41 DCHECK(thread_checker_.CalledOnValidThread());
42 return track_id_;
43 }
44
45 private:
46 // False until start/end of lifetime messages have been sent.
47 bool has_reported_start_;
48 bool has_reported_end_;
49
50 MediaStreamTrackMetrics::Direction direction_;
51 MediaStreamTrackMetrics::Kind kind_;
52 std::string track_id_;
53
54 // Non-owning.
55 MediaStreamTrackMetrics* owner_;
56 base::ThreadChecker thread_checker_;
57 };
58
59 namespace {
60
61 // Used with std::find_if.
62 struct ObserverFinder {
ObserverFinderblink::__anond875dd7a0111::ObserverFinder63 ObserverFinder(MediaStreamTrackMetrics::Direction direction,
64 MediaStreamTrackMetrics::Kind kind,
65 const std::string& track_id)
66 : direction_(direction), kind_(kind), track_id_(track_id) {}
operator ()blink::__anond875dd7a0111::ObserverFinder67 bool operator()(
68 const std::unique_ptr<MediaStreamTrackMetricsObserver>& observer) {
69 return direction_ == observer->direction() && kind_ == observer->kind() &&
70 track_id_ == observer->track_id();
71 }
72 MediaStreamTrackMetrics::Direction direction_;
73 MediaStreamTrackMetrics::Kind kind_;
74 std::string track_id_;
75 };
76
77 } // namespace
78
MediaStreamTrackMetricsObserver(MediaStreamTrackMetrics::Direction direction,MediaStreamTrackMetrics::Kind kind,std::string track_id,MediaStreamTrackMetrics * owner)79 MediaStreamTrackMetricsObserver::MediaStreamTrackMetricsObserver(
80 MediaStreamTrackMetrics::Direction direction,
81 MediaStreamTrackMetrics::Kind kind,
82 std::string track_id,
83 MediaStreamTrackMetrics* owner)
84 : has_reported_start_(false),
85 has_reported_end_(false),
86 direction_(direction),
87 kind_(kind),
88 track_id_(std::move(track_id)),
89 owner_(owner) {
90 DCHECK(owner);
91 }
92
~MediaStreamTrackMetricsObserver()93 MediaStreamTrackMetricsObserver::~MediaStreamTrackMetricsObserver() {
94 DCHECK(thread_checker_.CalledOnValidThread());
95 SendLifetimeMessageForTrack(
96 MediaStreamTrackMetrics::LifetimeEvent::kDisconnected);
97 }
98
SendLifetimeMessageForTrack(MediaStreamTrackMetrics::LifetimeEvent event)99 void MediaStreamTrackMetricsObserver::SendLifetimeMessageForTrack(
100 MediaStreamTrackMetrics::LifetimeEvent event) {
101 DCHECK(thread_checker_.CalledOnValidThread());
102 if (event == MediaStreamTrackMetrics::LifetimeEvent::kConnected) {
103 // Both ICE CONNECTED and COMPLETED can trigger the first
104 // start-of-life event, so we only report the first.
105 if (has_reported_start_)
106 return;
107 DCHECK(!has_reported_start_ && !has_reported_end_);
108 has_reported_start_ = true;
109 } else {
110 DCHECK(event == MediaStreamTrackMetrics::LifetimeEvent::kDisconnected);
111
112 // We only report the first end-of-life event, since there are
113 // several cases where end-of-life can be reached. We also don't
114 // report end unless we've reported start.
115 if (has_reported_end_ || !has_reported_start_)
116 return;
117 has_reported_end_ = true;
118 }
119
120 owner_->SendLifetimeMessage(track_id_, kind_, event, direction_);
121
122 if (event == MediaStreamTrackMetrics::LifetimeEvent::kDisconnected) {
123 // After disconnection, we can get reconnected, so we need to
124 // forget that we've sent lifetime events, while retaining all
125 // other state.
126 DCHECK(has_reported_start_ && has_reported_end_);
127 has_reported_start_ = false;
128 has_reported_end_ = false;
129 }
130 }
131
MediaStreamTrackMetrics()132 MediaStreamTrackMetrics::MediaStreamTrackMetrics()
133 : ice_state_(webrtc::PeerConnectionInterface::kIceConnectionNew) {}
134
~MediaStreamTrackMetrics()135 MediaStreamTrackMetrics::~MediaStreamTrackMetrics() {
136 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
137 for (const auto& observer : observers_) {
138 observer->SendLifetimeMessageForTrack(LifetimeEvent::kDisconnected);
139 }
140 }
141
AddTrack(Direction direction,Kind kind,const std::string & track_id)142 void MediaStreamTrackMetrics::AddTrack(Direction direction,
143 Kind kind,
144 const std::string& track_id) {
145 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
146 observers_.push_back(std::make_unique<MediaStreamTrackMetricsObserver>(
147 direction, kind, std::move(track_id), this));
148 SendLifeTimeMessageDependingOnIceState(observers_.back().get());
149 }
150
RemoveTrack(Direction direction,Kind kind,const std::string & track_id)151 void MediaStreamTrackMetrics::RemoveTrack(Direction direction,
152 Kind kind,
153 const std::string& track_id) {
154 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
155 auto it = std::find_if(observers_.begin(), observers_.end(),
156 ObserverFinder(direction, kind, track_id));
157 if (it == observers_.end()) {
158 // Since external apps could call removeTrack() with a stream they
159 // never added, this can happen without it being an error.
160 return;
161 }
162
163 observers_.erase(it);
164 }
165
IceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state)166 void MediaStreamTrackMetrics::IceConnectionChange(
167 webrtc::PeerConnectionInterface::IceConnectionState new_state) {
168 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
169 ice_state_ = new_state;
170 for (const auto& observer : observers_) {
171 SendLifeTimeMessageDependingOnIceState(observer.get());
172 }
173 }
174
SendLifeTimeMessageDependingOnIceState(MediaStreamTrackMetricsObserver * observer)175 void MediaStreamTrackMetrics::SendLifeTimeMessageDependingOnIceState(
176 MediaStreamTrackMetricsObserver* observer) {
177 // There is a state transition diagram for these states at
178 // http://dev.w3.org/2011/webrtc/editor/webrtc.html#idl-def-RTCIceConnectionState
179 switch (ice_state_) {
180 case webrtc::PeerConnectionInterface::kIceConnectionConnected:
181 case webrtc::PeerConnectionInterface::kIceConnectionCompleted:
182 observer->SendLifetimeMessageForTrack(LifetimeEvent::kConnected);
183 break;
184
185 case webrtc::PeerConnectionInterface::kIceConnectionFailed:
186 // We don't really need to handle FAILED (it is only supposed
187 // to be preceded by CHECKING so we wouldn't yet have sent a
188 // lifetime message) but we might as well use belt and
189 // suspenders and handle it the same as the other "end call"
190 // states. It will be ignored anyway if the call is not
191 // already connected.
192 case webrtc::PeerConnectionInterface::kIceConnectionNew:
193 // It's a bit weird to count NEW as an end-lifetime event, but
194 // it's possible to transition directly from a connected state
195 // (CONNECTED or COMPLETED) to NEW, which can then be followed
196 // by a new connection. The observer will ignore the end
197 // lifetime event if it was not preceded by a begin-lifetime
198 // event.
199 case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
200 case webrtc::PeerConnectionInterface::kIceConnectionClosed:
201 observer->SendLifetimeMessageForTrack(LifetimeEvent::kDisconnected);
202 break;
203
204 default:
205 // We ignore the remaining state (CHECKING) as it is never
206 // involved in a transition from connected to disconnected or
207 // vice versa.
208 break;
209 }
210 }
211
SendLifetimeMessage(const std::string & track_id,Kind kind,LifetimeEvent event,Direction direction)212 void MediaStreamTrackMetrics::SendLifetimeMessage(const std::string& track_id,
213 Kind kind,
214 LifetimeEvent event,
215 Direction direction) {
216 if (event == LifetimeEvent::kConnected) {
217 GetMediaStreamTrackMetricsHost()->AddTrack(
218 MakeUniqueId(track_id, direction), kind == Kind::kAudio,
219 direction == Direction::kReceive);
220 } else {
221 DCHECK_EQ(LifetimeEvent::kDisconnected, event);
222 GetMediaStreamTrackMetricsHost()->RemoveTrack(
223 MakeUniqueId(track_id, direction));
224 }
225 }
226
MakeUniqueIdImpl(uint64_t pc_id,const std::string & track_id,Direction direction)227 uint64_t MediaStreamTrackMetrics::MakeUniqueIdImpl(uint64_t pc_id,
228 const std::string& track_id,
229 Direction direction) {
230 // We use a hash over the |track| pointer and the PeerConnection ID,
231 // plus a boolean flag indicating whether the track is remote (since
232 // you might conceivably have a remote track added back as a sent
233 // track) as the unique ID.
234 //
235 // We don't need a cryptographically secure hash (which MD5 should
236 // no longer be considered), just one with virtually zero chance of
237 // collisions when faced with non-malicious data.
238 std::string unique_id_string =
239 base::StringPrintf("%" PRIu64 " %s %d", pc_id, track_id.c_str(),
240 direction == Direction::kReceive ? 1 : 0);
241
242 base::MD5Context ctx;
243 base::MD5Init(&ctx);
244 base::MD5Update(&ctx, unique_id_string);
245 base::MD5Digest digest;
246 base::MD5Final(&digest, &ctx);
247
248 static_assert(sizeof(digest.a) > sizeof(uint64_t), "need a bigger digest");
249 return *reinterpret_cast<uint64_t*>(digest.a);
250 }
251
MakeUniqueId(const std::string & track_id,Direction direction)252 uint64_t MediaStreamTrackMetrics::MakeUniqueId(const std::string& track_id,
253 Direction direction) {
254 return MakeUniqueIdImpl(
255 reinterpret_cast<uint64_t>(reinterpret_cast<void*>(this)), track_id,
256 direction);
257 }
258
259 mojo::Remote<blink::mojom::blink::MediaStreamTrackMetricsHost>&
GetMediaStreamTrackMetricsHost()260 MediaStreamTrackMetrics::GetMediaStreamTrackMetricsHost() {
261 if (!track_metrics_host_) {
262 blink::Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
263 track_metrics_host_.BindNewPipeAndPassReceiver());
264 }
265 return track_metrics_host_;
266 }
267
268 } // namespace blink
269