1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "host/frontend/webrtc/lib/client_handler.h"
18 
19 #include <optional>
20 #include <vector>
21 
22 #include <json/json.h>
23 #include <netdb.h>
24 #include <openssl/rand.h>
25 
26 #include <android-base/logging.h>
27 
28 #include "host/frontend/webrtc/lib/keyboard.h"
29 #include "host/libs/config/cuttlefish_config.h"
30 #include "https/SafeCallbackable.h"
31 
32 namespace cuttlefish {
33 namespace webrtc_streaming {
34 
35 namespace {
36 
37 static constexpr auto kInputChannelLabel = "input-channel";
38 static constexpr auto kAdbChannelLabel = "adb-channel";
39 
40 class ValidationResult {
41  public:
42   ValidationResult() = default;
ValidationResult(const std::string & error)43   ValidationResult(const std::string &error) : error_(error) {}
44 
ok() const45   bool ok() const { return !error_.has_value(); }
error() const46   std::string error() const { return error_.value_or(""); }
47 
48  private:
49   std::optional<std::string> error_;
50 };
51 
52 // helper method to ensure a json object has the required fields convertible
53 // to the appropriate types.
validateJsonObject(const Json::Value & obj,const std::string & type,const std::map<std::string,Json::ValueType> & fields)54 ValidationResult validateJsonObject(
55     const Json::Value &obj, const std::string &type,
56     const std::map<std::string, Json::ValueType> &fields) {
57   for (const auto &field_spec : fields) {
58     const auto &field_name = field_spec.first;
59     auto field_type = field_spec.second;
60     if (!(obj.isMember(field_name) &&
61           obj[field_name].isConvertibleTo(field_type))) {
62       std::string error_msg = "Expected a field named '";
63       error_msg += field_name + "' of type '";
64       error_msg += std::to_string(field_type);
65       error_msg += "'";
66       if (!type.empty()) {
67         error_msg += " in message of type '" + type + "'";
68       }
69       error_msg += ".";
70       return {error_msg};
71     }
72   }
73   return {};
74 }
75 
76 class CvdCreateSessionDescriptionObserver
77     : public webrtc::CreateSessionDescriptionObserver {
78  public:
CvdCreateSessionDescriptionObserver(std::weak_ptr<ClientHandler> client_handler)79   CvdCreateSessionDescriptionObserver(
80       std::weak_ptr<ClientHandler> client_handler)
81       : client_handler_(client_handler) {}
82 
OnSuccess(webrtc::SessionDescriptionInterface * desc)83   void OnSuccess(webrtc::SessionDescriptionInterface *desc) override {
84     auto client_handler = client_handler_.lock();
85     if (client_handler) {
86       client_handler->OnCreateSDPSuccess(desc);
87     }
88   }
OnFailure(webrtc::RTCError error)89   void OnFailure(webrtc::RTCError error) override {
90     auto client_handler = client_handler_.lock();
91     if (client_handler) {
92       client_handler->OnCreateSDPFailure(error);
93     }
94   }
95 
96  private:
97   std::weak_ptr<ClientHandler> client_handler_;
98 };
99 
100 class CvdSetSessionDescriptionObserver
101     : public webrtc::SetSessionDescriptionObserver {
102  public:
CvdSetSessionDescriptionObserver(std::weak_ptr<ClientHandler> client_handler)103   CvdSetSessionDescriptionObserver(std::weak_ptr<ClientHandler> client_handler)
104       : client_handler_(client_handler) {}
105 
OnSuccess()106   void OnSuccess() override {
107     // local description set, nothing else to do
108   }
OnFailure(webrtc::RTCError error)109   void OnFailure(webrtc::RTCError error) override {
110     auto client_handler = client_handler_.lock();
111     if (client_handler) {
112       client_handler->OnSetSDPFailure(error);
113     }
114   }
115 
116  private:
117   std::weak_ptr<ClientHandler> client_handler_;
118 };
119 
120 class CvdOnSetRemoteDescription
121     : public webrtc::SetRemoteDescriptionObserverInterface {
122  public:
CvdOnSetRemoteDescription(std::function<void (webrtc::RTCError error)> on_error)123   CvdOnSetRemoteDescription(
124       std::function<void(webrtc::RTCError error)> on_error)
125       : on_error_(on_error) {}
126 
OnSetRemoteDescriptionComplete(webrtc::RTCError error)127   void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override {
128     on_error_(error);
129   }
130 
131  private:
132   std::function<void(webrtc::RTCError error)> on_error_;
133 };
134 
135 }  // namespace
136 
InputHandler(rtc::scoped_refptr<webrtc::DataChannelInterface> input_channel,std::shared_ptr<ConnectionObserver> observer)137 ClientHandler::InputHandler::InputHandler(
138     rtc::scoped_refptr<webrtc::DataChannelInterface> input_channel,
139     std::shared_ptr<ConnectionObserver> observer)
140     : input_channel_(input_channel), observer_(observer) {
141   input_channel->RegisterObserver(this);
142 }
~InputHandler()143 ClientHandler::InputHandler::~InputHandler() {
144   input_channel_->UnregisterObserver();
145 }
OnStateChange()146 void ClientHandler::InputHandler::OnStateChange() {
147   LOG(VERBOSE) << "Input channel state changed to "
148                << webrtc::DataChannelInterface::DataStateString(
149                       input_channel_->state());
150 }
151 
OnMessage(const webrtc::DataBuffer & msg)152 void ClientHandler::InputHandler::OnMessage(const webrtc::DataBuffer &msg) {
153   if (msg.binary) {
154     // TODO (jemoreira) consider binary protocol to avoid JSON parsing overhead
155     LOG(ERROR) << "Received invalid (binary) data on input channel";
156     return;
157   }
158   auto size = msg.size();
159 
160   Json::Value evt;
161   Json::Reader json_reader;
162   auto str = msg.data.cdata<char>();
163   if (!json_reader.parse(str, str + size, evt) < 0) {
164     LOG(ERROR) << "Received invalid JSON object over input channel";
165     return;
166   }
167   if (!evt.isMember("type") || !evt["type"].isString()) {
168     LOG(ERROR) << "Input event doesn't have a valid 'type' field: "
169                << evt.toStyledString();
170     return;
171   }
172   auto event_type = evt["type"].asString();
173   if (event_type == "mouse") {
174     auto result =
175         validateJsonObject(evt, "mouse",
176                            {{"down", Json::ValueType::intValue},
177                             {"x", Json::ValueType::intValue},
178                             {"y", Json::ValueType::intValue},
179                             {"display_label", Json::ValueType::stringValue}});
180     if (!result.ok()) {
181       LOG(ERROR) << result.error();
182       return;
183     }
184     auto label = evt["display_label"].asString();
185     int32_t down = evt["down"].asInt();
186     int32_t x = evt["x"].asInt();
187     int32_t y = evt["y"].asInt();
188 
189     observer_->OnTouchEvent(label, x, y, down);
190   } else if (event_type == "multi-touch") {
191     auto result =
192         validateJsonObject(evt, "multi-touch",
193                            {{"id", Json::ValueType::intValue},
194                             {"initialDown", Json::ValueType::intValue},
195                             {"x", Json::ValueType::intValue},
196                             {"y", Json::ValueType::intValue},
197                             {"slot", Json::ValueType::intValue},
198                             {"display_label", Json::ValueType::stringValue}});
199     if (!result.ok()) {
200       LOG(ERROR) << result.error();
201       return;
202     }
203     auto label = evt["display_label"].asString();
204     int32_t id = evt["id"].asInt();
205     int32_t initialDown = evt["initialDown"].asInt();
206     int32_t x = evt["x"].asInt();
207     int32_t y = evt["y"].asInt();
208     int32_t slot = evt["slot"].asInt();
209 
210     observer_->OnMultiTouchEvent(label, id, slot, x, y, initialDown);
211   } else if (event_type == "keyboard") {
212     auto result =
213         validateJsonObject(evt, "keyboard",
214                            {{"event_type", Json::ValueType::stringValue},
215                             {"keycode", Json::ValueType::stringValue}});
216     if (!result.ok()) {
217       LOG(ERROR) << result.error();
218       return;
219     }
220     auto down = evt["event_type"].asString() == std::string("keydown");
221     auto code = DomKeyCodeToLinux(evt["keycode"].asString());
222     observer_->OnKeyboardEvent(code, down);
223   } else {
224     LOG(ERROR) << "Unrecognized event type: " << event_type;
225     return;
226   }
227 }
228 
AdbHandler(rtc::scoped_refptr<webrtc::DataChannelInterface> adb_channel,std::shared_ptr<ConnectionObserver> observer)229 ClientHandler::AdbHandler::AdbHandler(
230     rtc::scoped_refptr<webrtc::DataChannelInterface> adb_channel,
231     std::shared_ptr<ConnectionObserver> observer)
232     : adb_channel_(adb_channel), observer_(observer) {
233   adb_channel->RegisterObserver(this);
234 }
235 
~AdbHandler()236 ClientHandler::AdbHandler::~AdbHandler() { adb_channel_->UnregisterObserver(); }
237 
OnStateChange()238 void ClientHandler::AdbHandler::OnStateChange() {
239   LOG(VERBOSE) << "Input channel state changed to "
240                << webrtc::DataChannelInterface::DataStateString(
241                       adb_channel_->state());
242 }
243 
OnMessage(const webrtc::DataBuffer & msg)244 void ClientHandler::AdbHandler::OnMessage(const webrtc::DataBuffer &msg) {
245   // Report the adb channel as open on the first message received instead of at
246   // channel open, this avoids unnecessarily connecting to the adb daemon for
247   // clients that don't use ADB.
248   if (!channel_open_reported_) {
249     observer_->OnAdbChannelOpen([this](const uint8_t *msg, size_t size) {
250       webrtc::DataBuffer buffer(rtc::CopyOnWriteBuffer(msg, size),
251                                 true /*binary*/);
252       // TODO (jemoreira): When the SCTP channel is congested data channel
253       // messages are buffered up to 16MB, when the buffer is full the channel
254       // is abruptly closed. Keep track of the buffered data to avoid losing the
255       // adb data channel.
256       adb_channel_->Send(buffer);
257       return true;
258     });
259     channel_open_reported_ = true;
260   }
261   observer_->OnAdbMessage(msg.data.cdata(), msg.size());
262 }
263 
Create(int client_id,std::shared_ptr<ConnectionObserver> observer,std::function<void (const Json::Value &)> send_to_client_cb,std::function<void ()> on_connection_closed_cb)264 std::shared_ptr<ClientHandler> ClientHandler::Create(
265     int client_id, std::shared_ptr<ConnectionObserver> observer,
266     std::function<void(const Json::Value &)> send_to_client_cb,
267     std::function<void()> on_connection_closed_cb) {
268   return std::shared_ptr<ClientHandler>(new ClientHandler(
269       client_id, observer, send_to_client_cb, on_connection_closed_cb));
270 }
271 
ClientHandler(int client_id,std::shared_ptr<ConnectionObserver> observer,std::function<void (const Json::Value &)> send_to_client_cb,std::function<void ()> on_connection_closed_cb)272 ClientHandler::ClientHandler(
273     int client_id, std::shared_ptr<ConnectionObserver> observer,
274     std::function<void(const Json::Value &)> send_to_client_cb,
275     std::function<void()> on_connection_closed_cb)
276     : client_id_(client_id),
277       observer_(observer),
278       send_to_client_(send_to_client_cb),
279       on_connection_closed_cb_(on_connection_closed_cb) {}
280 
~ClientHandler()281 ClientHandler::~ClientHandler() {
282   for (auto &data_channel : data_channels_) {
283     data_channel->UnregisterObserver();
284   }
285 }
286 
SetPeerConnection(rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection)287 bool ClientHandler::SetPeerConnection(
288     rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection) {
289   peer_connection_ = peer_connection;
290 
291   // If no channel is created on the peer connection the generated offer won't
292   // have an entry for data channels which breaks input and adb.
293   // This channel has no use now, but could be used in the future to exchange
294   // control data between client and device without going through the signaling
295   // server.
296   auto control_channel = peer_connection_->CreateDataChannel(
297       "device-control", nullptr /* config */);
298   if (!control_channel) {
299     LOG(ERROR) << "Failed to create control data channel";
300     return false;
301   }
302   return true;
303 }
304 
AddDisplay(rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track,const std::string & label)305 bool ClientHandler::AddDisplay(
306     rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track,
307     const std::string &label) {
308   // Send each track as part of a different stream with the label as id
309   auto err_or_sender =
310       peer_connection_->AddTrack(video_track, {label} /* stream_id */);
311   if (!err_or_sender.ok()) {
312     LOG(ERROR) << "Failed to add video track to the peer connection";
313     return false;
314   }
315   // TODO (b/154138394): use the returned sender (err_or_sender.value()) to
316   // remove the display from the connection.
317   return true;
318 }
319 
LogAndReplyError(const std::string & error_msg) const320 void ClientHandler::LogAndReplyError(const std::string &error_msg) const {
321   LOG(ERROR) << error_msg;
322   Json::Value reply;
323   reply["type"] = "error";
324   reply["error"] = error_msg;
325   send_to_client_(reply);
326 }
327 
OnCreateSDPSuccess(webrtc::SessionDescriptionInterface * desc)328 void ClientHandler::OnCreateSDPSuccess(
329     webrtc::SessionDescriptionInterface *desc) {
330   std::string offer_str;
331   desc->ToString(&offer_str);
332   peer_connection_->SetLocalDescription(
333       // The peer connection wraps this raw pointer with a scoped_refptr, so
334       // it's guaranteed to be deleted at some point
335       new rtc::RefCountedObject<CvdSetSessionDescriptionObserver>(
336           weak_from_this()),
337       desc);
338   // The peer connection takes ownership of the description so it should not be
339   // used after this
340   desc = nullptr;
341 
342   Json::Value reply;
343   reply["type"] = "offer";
344   reply["sdp"] = offer_str;
345 
346   send_to_client_(reply);
347 }
348 
OnCreateSDPFailure(webrtc::RTCError error)349 void ClientHandler::OnCreateSDPFailure(webrtc::RTCError error) {
350   LogAndReplyError(error.message());
351   Close();
352 }
353 
OnSetSDPFailure(webrtc::RTCError error)354 void ClientHandler::OnSetSDPFailure(webrtc::RTCError error) {
355   LogAndReplyError(error.message());
356   LOG(ERROR) << "Error setting local description: Either there is a bug in "
357                 "libwebrtc or the local description was (incorrectly) modified "
358                 "after creating it";
359   Close();
360 }
361 
HandleMessage(const Json::Value & message)362 void ClientHandler::HandleMessage(const Json::Value &message) {
363   {
364     auto result = validateJsonObject(message, "",
365                                      {{"type", Json::ValueType::stringValue}});
366     if (!result.ok()) {
367       LogAndReplyError(result.error());
368       return;
369     }
370   }
371   auto type = message["type"].asString();
372   if (type == "request-offer") {
373     peer_connection_->CreateOffer(
374         // No memory leak here because this is a ref counted objects and the
375         // peer connection immediately wraps it with a scoped_refptr
376         new rtc::RefCountedObject<CvdCreateSessionDescriptionObserver>(
377             weak_from_this()),
378         webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
379     // The created offer wil be sent to the client on
380     // OnSuccess(webrtc::SessionDescriptionInterface* desc)
381   } else if (type == "answer") {
382     auto result = validateJsonObject(message, type,
383                                      {{"sdp", Json::ValueType::stringValue}});
384     if (!result.ok()) {
385       LogAndReplyError(result.error());
386       return;
387     }
388     auto remote_desc_str = message["sdp"].asString();
389     auto remote_desc = webrtc::CreateSessionDescription(
390         webrtc::SdpType::kAnswer, remote_desc_str, nullptr /*error*/);
391     if (!remote_desc) {
392       LogAndReplyError("Failed to parse answer.");
393       return;
394     }
395     rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface> observer(
396         new rtc::RefCountedObject<CvdOnSetRemoteDescription>(
397             [this](webrtc::RTCError error) {
398               if (!error.ok()) {
399                 LogAndReplyError(error.message());
400                 // The remote description was rejected, this client can't be
401                 // trusted anymore.
402                 Close();
403               }
404             }));
405     peer_connection_->SetRemoteDescription(std::move(remote_desc), observer);
406 
407   } else if (type == "ice-candidate") {
408     {
409       auto result = validateJsonObject(
410           message, type, {{"candidate", Json::ValueType::objectValue}});
411       if (!result.ok()) {
412         LogAndReplyError(result.error());
413         return;
414       }
415     }
416     auto candidate_json = message["candidate"];
417     {
418       auto result =
419           validateJsonObject(candidate_json, "ice-candidate/candidate",
420                              {
421                                  {"sdpMid", Json::ValueType::stringValue},
422                                  {"candidate", Json::ValueType::stringValue},
423                                  {"sdpMLineIndex", Json::ValueType::intValue},
424                              });
425       if (!result.ok()) {
426         LogAndReplyError(result.error());
427         return;
428       }
429     }
430     auto mid = candidate_json["sdpMid"].asString();
431     auto candidate_sdp = candidate_json["candidate"].asString();
432     auto line_index = candidate_json["sdpMLineIndex"].asInt();
433 
434     std::unique_ptr<webrtc::IceCandidateInterface> candidate(
435         webrtc::CreateIceCandidate(mid, line_index, candidate_sdp,
436                                    nullptr /*error*/));
437     if (!candidate) {
438       LogAndReplyError("Failed to parse ICE candidate");
439       return;
440     }
441     peer_connection_->AddIceCandidate(std::move(candidate),
442                                       [this](webrtc::RTCError error) {
443                                         if (!error.ok()) {
444                                           LogAndReplyError(error.message());
445                                         }
446                                       });
447   } else {
448     LogAndReplyError("Unknown client message type: " + type);
449     return;
450   }
451 }
452 
Close()453 void ClientHandler::Close() {
454   // We can't simply call peer_connection_->Close() here because this method
455   // could be called from one of the PeerConnectionObserver callbacks and that
456   // would lead to a deadlock (Close eventually tries to destroy an object that
457   // will then wait for the callback to return -> deadlock). Destroying the
458   // peer_connection_ has the same effect. The only alternative is to postpone
459   // that operation until after the callback returns.
460   on_connection_closed_cb_();
461 }
462 
OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state)463 void ClientHandler::OnConnectionChange(
464     webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
465   switch (new_state) {
466     case webrtc::PeerConnectionInterface::PeerConnectionState::kNew:
467       break;
468     case webrtc::PeerConnectionInterface::PeerConnectionState::kConnecting:
469       break;
470     case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
471       LOG(VERBOSE) << "Client " << client_id_ << ": WebRTC connected";
472       observer_->OnConnected();
473       break;
474     case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected:
475       LOG(VERBOSE) << "Client " << client_id_ << ": Connection disconnected";
476       Close();
477       break;
478     case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
479       LOG(ERROR) << "Client " << client_id_ << ": Connection failed";
480       Close();
481       break;
482     case webrtc::PeerConnectionInterface::PeerConnectionState::kClosed:
483       LOG(VERBOSE) << "Client " << client_id_ << ": Connection closed";
484       Close();
485       break;
486   }
487 }
488 
OnIceCandidate(const webrtc::IceCandidateInterface * candidate)489 void ClientHandler::OnIceCandidate(
490     const webrtc::IceCandidateInterface *candidate) {
491   std::string candidate_sdp;
492   candidate->ToString(&candidate_sdp);
493   auto sdp_mid = candidate->sdp_mid();
494   auto line_index = candidate->sdp_mline_index();
495 
496   Json::Value reply;
497   reply["type"] = "ice-candidate";
498   reply["mid"] = sdp_mid;
499   reply["mLineIndex"] = static_cast<Json::UInt64>(line_index);
500   reply["candidate"] = candidate_sdp;
501 
502   send_to_client_(reply);
503 }
504 
OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)505 void ClientHandler::OnDataChannel(
506     rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
507   auto label = data_channel->label();
508   if (label == kInputChannelLabel) {
509     input_handler_.reset(new InputHandler(data_channel, observer_));
510   } else if (label == kAdbChannelLabel) {
511     adb_handler_.reset(new AdbHandler(data_channel, observer_));
512   } else {
513     LOG(VERBOSE) << "Data channel connected: " << label;
514     data_channels_.push_back(data_channel);
515   }
516 }
517 
OnRenegotiationNeeded()518 void ClientHandler::OnRenegotiationNeeded() {
519   LOG(VERBOSE) << "Client " << client_id_ << " needs renegotiation";
520 }
521 
OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state)522 void ClientHandler::OnIceGatheringChange(
523     webrtc::PeerConnectionInterface::IceGatheringState new_state) {
524   std::string state_str;
525   switch (new_state) {
526     case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringNew:
527       state_str = "NEW";
528       break;
529     case webrtc::PeerConnectionInterface::IceGatheringState::
530         kIceGatheringGathering:
531       state_str = "GATHERING";
532       break;
533     case webrtc::PeerConnectionInterface::IceGatheringState::
534         kIceGatheringComplete:
535       state_str = "COMPLETE";
536       break;
537     default:
538       state_str = "UNKNOWN";
539   }
540   LOG(VERBOSE) << "Client " << client_id_
541                << ": ICE Gathering state set to: " << state_str;
542 }
543 
OnIceCandidateError(const std::string & host_candidate,const std::string & url,int error_code,const std::string & error_text)544 void ClientHandler::OnIceCandidateError(const std::string &host_candidate,
545                                         const std::string &url, int error_code,
546                                         const std::string &error_text) {
547   LOG(VERBOSE) << "Gathering of an ICE candidate (host candidate: "
548                << host_candidate << ", url: " << url
549                << ") failed: " << error_text;
550 }
551 
OnIceCandidateError(const std::string & address,int port,const std::string & url,int error_code,const std::string & error_text)552 void ClientHandler::OnIceCandidateError(const std::string &address, int port,
553                                         const std::string &url, int error_code,
554                                         const std::string &error_text) {
555   LOG(VERBOSE) << "Gathering of an ICE candidate (address: " << address
556                << ", port: " << port << ", url: " << url
557                << ") failed: " << error_text;
558 }
559 
OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state)560 void ClientHandler::OnSignalingChange(
561     webrtc::PeerConnectionInterface::SignalingState new_state) {
562   // ignore
563 }
OnStandardizedIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state)564 void ClientHandler::OnStandardizedIceConnectionChange(
565     webrtc::PeerConnectionInterface::IceConnectionState new_state) {
566   switch (new_state) {
567     case webrtc::PeerConnectionInterface::kIceConnectionNew:
568       LOG(DEBUG) << "ICE connection state: New";
569       break;
570     case webrtc::PeerConnectionInterface::kIceConnectionChecking:
571       LOG(DEBUG) << "ICE connection state: Checking";
572       break;
573     case webrtc::PeerConnectionInterface::kIceConnectionConnected:
574       LOG(DEBUG) << "ICE connection state: Connected";
575       break;
576     case webrtc::PeerConnectionInterface::kIceConnectionCompleted:
577       LOG(DEBUG) << "ICE connection state: Completed";
578       break;
579     case webrtc::PeerConnectionInterface::kIceConnectionFailed:
580       LOG(DEBUG) << "ICE connection state: Failed";
581       break;
582     case webrtc::PeerConnectionInterface::kIceConnectionDisconnected:
583       LOG(DEBUG) << "ICE connection state: Disconnected";
584       break;
585     case webrtc::PeerConnectionInterface::kIceConnectionClosed:
586       LOG(DEBUG) << "ICE connection state: Closed";
587       break;
588     case webrtc::PeerConnectionInterface::kIceConnectionMax:
589       LOG(DEBUG) << "ICE connection state: Max";
590       break;
591   }
592 }
OnIceCandidatesRemoved(const std::vector<cricket::Candidate> & candidates)593 void ClientHandler::OnIceCandidatesRemoved(
594     const std::vector<cricket::Candidate> &candidates) {
595   // ignore
596 }
OnTrack(rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver)597 void ClientHandler::OnTrack(
598     rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
599   // ignore
600 }
OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver)601 void ClientHandler::OnRemoveTrack(
602     rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
603   // ignore
604 }
605 
606 }  // namespace webrtc_streaming
607 }  // namespace cuttlefish
608