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