1 // Copyright 2019 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 "remoting/signaling/ftl_signal_strategy.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/logging.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/observer_list.h"
14 #include "base/rand_util.h"
15 #include "base/sequence_checker.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/threading/sequenced_task_runner_handle.h"
18 #include "remoting/base/logging.h"
19 #include "remoting/base/oauth_token_getter.h"
20 #include "remoting/base/protobuf_http_status.h"
21 #include "remoting/signaling/ftl_device_id_provider.h"
22 #include "remoting/signaling/ftl_messaging_client.h"
23 #include "remoting/signaling/ftl_registration_manager.h"
24 #include "remoting/signaling/signaling_address.h"
25 #include "services/network/public/cpp/shared_url_loader_factory.h"
26 #include "third_party/libjingle_xmpp/xmllite/xmlelement.h"
27 #include "third_party/libjingle_xmpp/xmpp/constants.h"
28
29 namespace remoting {
30
31 class FtlSignalStrategy::Core {
32 public:
33 Core(std::unique_ptr<OAuthTokenGetter> oauth_token_getter,
34 std::unique_ptr<RegistrationManager> registration_manager,
35 std::unique_ptr<MessagingClient> messaging_client);
36 ~Core();
37
38 void Connect();
39 void Disconnect();
40 State GetState() const;
41 Error GetError() const;
42 const SignalingAddress& GetLocalAddress() const;
43 void AddListener(Listener* listener);
44 void RemoveListener(Listener* listener);
45 bool SendStanza(std::unique_ptr<jingle_xmpp::XmlElement> stanza);
46 bool SendMessage(const SignalingAddress& destination_address,
47 const ftl::ChromotingMessage& message);
48 bool IsSignInError() const;
49
50 private:
51 // Methods are called in the order below when Connect() is called.
52 void OnGetOAuthTokenResponse(OAuthTokenGetter::Status status,
53 const std::string& user_email,
54 const std::string& access_token);
55 void OnSignInGaiaResponse(const ProtobufHttpStatus& status);
56 void StartReceivingMessages();
57 void OnReceiveMessagesStreamStarted();
58 void OnReceiveMessagesStreamClosed(const ProtobufHttpStatus& status);
59 void OnMessageReceived(const ftl::Id& sender_id,
60 const std::string& sender_registration_id,
61 const ftl::ChromotingMessage& message);
62
63 void SendMessageImpl(const SignalingAddress& receiver,
64 const ftl::ChromotingMessage& message,
65 MessagingClient::DoneCallback callback);
66 void OnSendMessageResponse(const SignalingAddress& receiver,
67 const std::string& stanza_id,
68 const ProtobufHttpStatus& status);
69
70 // Returns true if the status is handled.
71 void HandleProtobufHttpStatusError(const base::Location& location,
72 const ProtobufHttpStatus& status);
73
74 void OnStanza(const SignalingAddress& sender_address,
75 std::unique_ptr<jingle_xmpp::XmlElement> stanza);
76
77 std::unique_ptr<OAuthTokenGetter> oauth_token_getter_;
78
79 std::unique_ptr<RegistrationManager> registration_manager_;
80 std::unique_ptr<MessagingClient> messaging_client_;
81
82 std::string user_email_;
83 SignalingAddress local_address_;
84
85 std::unique_ptr<MessagingClient::MessageCallbackSubscription>
86 receive_message_subscription_;
87
88 Error error_ = OK;
89 bool is_sign_in_error_ = false;
90
91 base::ObserverList<Listener, true> listeners_;
92
93 SEQUENCE_CHECKER(sequence_checker_);
94
95 base::WeakPtrFactory<Core> weak_factory_{this};
96 DISALLOW_COPY_AND_ASSIGN(Core);
97 };
98
Core(std::unique_ptr<OAuthTokenGetter> oauth_token_getter,std::unique_ptr<RegistrationManager> registration_manager,std::unique_ptr<MessagingClient> messaging_client)99 FtlSignalStrategy::Core::Core(
100 std::unique_ptr<OAuthTokenGetter> oauth_token_getter,
101 std::unique_ptr<RegistrationManager> registration_manager,
102 std::unique_ptr<MessagingClient> messaging_client) {
103 DCHECK(oauth_token_getter);
104 DCHECK(registration_manager);
105 DCHECK(messaging_client);
106 oauth_token_getter_ = std::move(oauth_token_getter);
107 registration_manager_ = std::move(registration_manager);
108 messaging_client_ = std::move(messaging_client);
109 }
110
~Core()111 FtlSignalStrategy::Core::~Core() {
112 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
113 Disconnect();
114 }
115
Connect()116 void FtlSignalStrategy::Core::Connect() {
117 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
118
119 if (GetState() != DISCONNECTED) {
120 LOG(WARNING) << "Signaling is not disconnected. State: " << GetState();
121 return;
122 }
123
124 error_ = OK;
125 is_sign_in_error_ = false;
126
127 receive_message_subscription_ =
128 messaging_client_->RegisterMessageCallback(base::BindRepeating(
129 &Core::OnMessageReceived, weak_factory_.GetWeakPtr()));
130
131 for (auto& observer : listeners_)
132 observer.OnSignalStrategyStateChange(CONNECTING);
133
134 StartReceivingMessages();
135 }
136
Disconnect()137 void FtlSignalStrategy::Core::Disconnect() {
138 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
139
140 if (registration_manager_->IsSignedIn()) {
141 registration_manager_->SignOut();
142 }
143
144 if (receive_message_subscription_) {
145 local_address_ = SignalingAddress();
146 receive_message_subscription_.reset();
147 messaging_client_->StopReceivingMessages();
148
149 for (auto& observer : listeners_)
150 observer.OnSignalStrategyStateChange(DISCONNECTED);
151 }
152 }
153
GetState() const154 SignalStrategy::State FtlSignalStrategy::Core::GetState() const {
155 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
156
157 if (!local_address_.empty()) {
158 DCHECK(receive_message_subscription_);
159 return CONNECTED;
160 } else if (receive_message_subscription_) {
161 return CONNECTING;
162 } else {
163 return DISCONNECTED;
164 }
165 }
166
GetError() const167 SignalStrategy::Error FtlSignalStrategy::Core::GetError() const {
168 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
169 return error_;
170 }
171
GetLocalAddress() const172 const SignalingAddress& FtlSignalStrategy::Core::GetLocalAddress() const {
173 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
174 return local_address_;
175 }
176
AddListener(Listener * listener)177 void FtlSignalStrategy::Core::AddListener(Listener* listener) {
178 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
179 listeners_.AddObserver(listener);
180 }
181
RemoveListener(Listener * listener)182 void FtlSignalStrategy::Core::RemoveListener(Listener* listener) {
183 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
184 listeners_.RemoveObserver(listener);
185 }
186
SendStanza(std::unique_ptr<jingle_xmpp::XmlElement> stanza)187 bool FtlSignalStrategy::Core::SendStanza(
188 std::unique_ptr<jingle_xmpp::XmlElement> stanza) {
189 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
190
191 if (GetState() != CONNECTED) {
192 HOST_LOG << "Dropping signaling message because FTL is not connected.";
193 return false;
194 }
195
196 std::string to_error;
197 SignalingAddress to =
198 SignalingAddress::Parse(stanza.get(), SignalingAddress::TO, &to_error);
199 DCHECK(to_error.empty());
200
201 // Synthesizing the from attribute in the message.
202 stanza->SetAttr(jingle_xmpp::QN_FROM, local_address_.id());
203
204 std::string stanza_id = stanza->Attr(jingle_xmpp::QN_ID);
205
206 ftl::ChromotingMessage crd_message;
207 crd_message.mutable_xmpp()->set_stanza(stanza->Str());
208 SendMessageImpl(to, crd_message,
209 base::BindOnce(&Core::OnSendMessageResponse,
210 weak_factory_.GetWeakPtr(), to, stanza_id));
211
212 // Return false if the SendMessageImpl() call above resulted in the
213 // SignalStrategy being disconnected.
214 return GetState() == CONNECTED;
215 }
216
SendMessage(const SignalingAddress & destination_address,const ftl::ChromotingMessage & message)217 bool FtlSignalStrategy::Core::SendMessage(
218 const SignalingAddress& destination_address,
219 const ftl::ChromotingMessage& message) {
220 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
221
222 if (GetState() != CONNECTED) {
223 HOST_LOG << "Dropping message because FTL is not connected.";
224 return false;
225 }
226
227 SendMessageImpl(
228 destination_address, message,
229 base::BindOnce(&Core::OnSendMessageResponse, weak_factory_.GetWeakPtr(),
230 destination_address, std::string()));
231
232 return true;
233 }
234
IsSignInError() const235 bool FtlSignalStrategy::Core::IsSignInError() const {
236 return is_sign_in_error_;
237 }
238
OnGetOAuthTokenResponse(OAuthTokenGetter::Status status,const std::string & user_email,const std::string & access_token)239 void FtlSignalStrategy::Core::OnGetOAuthTokenResponse(
240 OAuthTokenGetter::Status status,
241 const std::string& user_email,
242 const std::string& access_token) {
243 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
244 if (status != OAuthTokenGetter::Status::SUCCESS) {
245 switch (status) {
246 case OAuthTokenGetter::Status::NETWORK_ERROR:
247 error_ = SignalStrategy::Error::NETWORK_ERROR;
248 break;
249 case OAuthTokenGetter::Status::AUTH_ERROR:
250 error_ = SignalStrategy::Error::AUTHENTICATION_FAILED;
251 break;
252 default:
253 NOTREACHED();
254 break;
255 }
256 is_sign_in_error_ = true;
257 Disconnect();
258 return;
259 }
260
261 user_email_ = user_email;
262 StartReceivingMessages();
263 }
264
OnSignInGaiaResponse(const ProtobufHttpStatus & status)265 void FtlSignalStrategy::Core::OnSignInGaiaResponse(
266 const ProtobufHttpStatus& status) {
267 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
268 if (!status.ok()) {
269 is_sign_in_error_ = true;
270 HandleProtobufHttpStatusError(FROM_HERE, status);
271 return;
272 }
273 StartReceivingMessages();
274 }
275
StartReceivingMessages()276 void FtlSignalStrategy::Core::StartReceivingMessages() {
277 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
278 DCHECK_EQ(CONNECTING, GetState());
279 DCHECK(!messaging_client_->IsReceivingMessages());
280
281 if (user_email_.empty()) {
282 oauth_token_getter_->CallWithToken(base::BindOnce(
283 &Core::OnGetOAuthTokenResponse, weak_factory_.GetWeakPtr()));
284 return;
285 }
286
287 if (!registration_manager_->IsSignedIn()) {
288 registration_manager_->SignInGaia(base::BindOnce(
289 &Core::OnSignInGaiaResponse, weak_factory_.GetWeakPtr()));
290 return;
291 }
292
293 messaging_client_->StartReceivingMessages(
294 base::BindOnce(&Core::OnReceiveMessagesStreamStarted,
295 weak_factory_.GetWeakPtr()),
296 base::BindOnce(&Core::OnReceiveMessagesStreamClosed,
297 weak_factory_.GetWeakPtr()));
298 }
299
OnReceiveMessagesStreamStarted()300 void FtlSignalStrategy::Core::OnReceiveMessagesStreamStarted() {
301 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
302 local_address_ = SignalingAddress::CreateFtlSignalingAddress(
303 user_email_, registration_manager_->GetRegistrationId());
304
305 for (auto& observer : listeners_)
306 observer.OnSignalStrategyStateChange(CONNECTED);
307 }
308
OnReceiveMessagesStreamClosed(const ProtobufHttpStatus & status)309 void FtlSignalStrategy::Core::OnReceiveMessagesStreamClosed(
310 const ProtobufHttpStatus& status) {
311 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
312 if (status.error_code() == ProtobufHttpStatus::Code::CANCELLED) {
313 LOG(WARNING) << "ReceiveMessages stream closed with CANCELLED code.";
314 }
315 DCHECK(!status.ok());
316 HandleProtobufHttpStatusError(FROM_HERE, status);
317 }
318
OnMessageReceived(const ftl::Id & sender_id,const std::string & sender_registration_id,const ftl::ChromotingMessage & message)319 void FtlSignalStrategy::Core::OnMessageReceived(
320 const ftl::Id& sender_id,
321 const std::string& sender_registration_id,
322 const ftl::ChromotingMessage& message) {
323 for (auto& listener : listeners_) {
324 if (listener.OnSignalStrategyIncomingMessage(
325 sender_id, sender_registration_id, message)) {
326 return;
327 }
328 }
329
330 if (!message.has_xmpp()) {
331 LOG(WARNING) << "Ignoring message that doesn't have XMPP field.";
332 return;
333 }
334
335 auto sender_address = SignalingAddress::CreateFtlSignalingAddress(
336 sender_id.id(), sender_registration_id);
337 DCHECK(message.xmpp().has_stanza());
338 auto stanza = base::WrapUnique<jingle_xmpp::XmlElement>(
339 jingle_xmpp::XmlElement::ForStr(message.xmpp().stanza()));
340 if (!stanza) {
341 LOG(WARNING) << "Failed to parse XMPP: " << message.xmpp().stanza();
342 return;
343 }
344 OnStanza(sender_address, std::move(stanza));
345 }
346
SendMessageImpl(const SignalingAddress & receiver,const ftl::ChromotingMessage & message,MessagingClient::DoneCallback callback)347 void FtlSignalStrategy::Core::SendMessageImpl(
348 const SignalingAddress& receiver,
349 const ftl::ChromotingMessage& message,
350 MessagingClient::DoneCallback callback) {
351 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
352
353 std::string receiver_username;
354 std::string receiver_registration_id;
355 bool get_info_result =
356 receiver.GetFtlInfo(&receiver_username, &receiver_registration_id);
357 if (!get_info_result) {
358 LOG(DFATAL) << "Receiver is not in FTL address: " << receiver.id();
359 return;
360 }
361
362 std::string message_payload;
363 if (message.has_xmpp()) {
364 message_payload = message.xmpp().stanza();
365 } else if (message.has_echo()) {
366 message_payload = message.echo().message();
367 } else {
368 message_payload = "Error displaying message due to unknown format.";
369 }
370
371 HOST_LOG << "Sending outgoing message:\n"
372 << "Receiver: " << receiver_username << "\n"
373 << "Receiver registration ID: " << receiver_registration_id << "\n"
374 << message_payload
375 << "\n=========================================================";
376
377 messaging_client_->SendMessage(receiver_username, receiver_registration_id,
378 message, std::move(callback));
379 }
380
OnSendMessageResponse(const SignalingAddress & receiver,const std::string & stanza_id,const ProtobufHttpStatus & status)381 void FtlSignalStrategy::Core::OnSendMessageResponse(
382 const SignalingAddress& receiver,
383 const std::string& stanza_id,
384 const ProtobufHttpStatus& status) {
385 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
386 if (status.ok()) {
387 return;
388 }
389
390 if (status.error_code() == ProtobufHttpStatus::Code::UNAUTHENTICATED) {
391 HandleProtobufHttpStatusError(FROM_HERE, status);
392 return;
393 }
394
395 LOG(ERROR) << "Failed to send message to peer. Error code: "
396 << static_cast<int>(status.error_code())
397 << ", message: " << status.error_message();
398
399 if (stanza_id.empty()) {
400 // If the message sent was not related to signaling, then exit early.
401 return;
402 }
403
404 // Fake an error message so JingleSession will take it as PEER_IS_OFFLINE.
405 auto error_iq = std::make_unique<jingle_xmpp::XmlElement>(jingle_xmpp::QN_IQ);
406 error_iq->SetAttr(jingle_xmpp::QN_TYPE, jingle_xmpp::STR_ERROR);
407 error_iq->SetAttr(jingle_xmpp::QN_ID, stanza_id);
408 error_iq->SetAttr(jingle_xmpp::QN_FROM, receiver.id());
409 error_iq->SetAttr(jingle_xmpp::QN_TO, local_address_.id());
410 OnStanza(receiver, std::move(error_iq));
411 }
412
HandleProtobufHttpStatusError(const base::Location & location,const ProtobufHttpStatus & status)413 void FtlSignalStrategy::Core::HandleProtobufHttpStatusError(
414 const base::Location& location,
415 const ProtobufHttpStatus& status) {
416 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
417 DCHECK(!status.ok());
418 // We don't map HTTP_UNAUTHORIZED to AUTHENTICATION_FAILED here, as it will
419 // permanently terminate the host, which is not desirable since it might
420 // happen when the FTL registration becomes invalid while the robot account
421 // itself is still intact.
422 // AUTHENTICATION_FAILED is only reported if the OAuthTokenGetter fails to
423 // fetch the token.
424 error_ = Error::NETWORK_ERROR;
425 LOG(ERROR) << "Received server error. Error code: "
426 << static_cast<int>(status.error_code())
427 << ", message: " << status.error_message()
428 << ", location: " << location.ToString();
429 if (status.error_code() == ProtobufHttpStatus::Code::UNAUTHENTICATED ||
430 status.error_code() == ProtobufHttpStatus::Code::PERMISSION_DENIED) {
431 oauth_token_getter_->InvalidateCache();
432 }
433 Disconnect();
434 }
435
OnStanza(const SignalingAddress & sender_address,std::unique_ptr<jingle_xmpp::XmlElement> stanza)436 void FtlSignalStrategy::Core::OnStanza(
437 const SignalingAddress& sender_address,
438 std::unique_ptr<jingle_xmpp::XmlElement> stanza) {
439 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
440
441 // Validate the schema and FTL IDs.
442 if (stanza->Name() != jingle_xmpp::QN_IQ) {
443 LOG(DFATAL) << "Received unexpected non-IQ packet " << stanza->Str();
444 return;
445 }
446 if (SignalingAddress(stanza->Attr(jingle_xmpp::QN_FROM)) != sender_address) {
447 LOG(DFATAL) << "Expected sender: " << sender_address.id()
448 << ", but received: " << stanza->Attr(jingle_xmpp::QN_FROM);
449 return;
450 }
451 if (SignalingAddress(stanza->Attr(jingle_xmpp::QN_TO)) != local_address_) {
452 LOG(DFATAL) << "Expected receiver: " << local_address_.id()
453 << ", but received: " << stanza->Attr(jingle_xmpp::QN_TO);
454 return;
455 }
456
457 HOST_LOG << "Received incoming stanza:\n"
458 << stanza->Str()
459 << "\n=========================================================";
460
461 for (auto& listener : listeners_) {
462 if (listener.OnSignalStrategyIncomingStanza(stanza.get()))
463 return;
464 }
465 }
466
FtlSignalStrategy(std::unique_ptr<OAuthTokenGetter> oauth_token_getter,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,std::unique_ptr<FtlDeviceIdProvider> device_id_provider,SignalingTracker * signaling_tracker)467 FtlSignalStrategy::FtlSignalStrategy(
468 std::unique_ptr<OAuthTokenGetter> oauth_token_getter,
469 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
470 std::unique_ptr<FtlDeviceIdProvider> device_id_provider,
471 SignalingTracker* signaling_tracker) {
472 // TODO(yuweih): Just make FtlMessagingClient own FtlRegistrationManager and
473 // call SignInGaia() transparently.
474 auto registration_manager = std::make_unique<FtlRegistrationManager>(
475 oauth_token_getter.get(), url_loader_factory,
476 std::move(device_id_provider));
477 auto messaging_client = std::make_unique<FtlMessagingClient>(
478 oauth_token_getter.get(), url_loader_factory, registration_manager.get(),
479 signaling_tracker);
480 CreateCore(std::move(oauth_token_getter), std::move(registration_manager),
481 std::move(messaging_client));
482 }
483
FtlSignalStrategy(std::unique_ptr<OAuthTokenGetter> oauth_token_getter,std::unique_ptr<RegistrationManager> registration_manager,std::unique_ptr<MessagingClient> messaging_client)484 FtlSignalStrategy::FtlSignalStrategy(
485 std::unique_ptr<OAuthTokenGetter> oauth_token_getter,
486 std::unique_ptr<RegistrationManager> registration_manager,
487 std::unique_ptr<MessagingClient> messaging_client) {
488 CreateCore(std::move(oauth_token_getter), std::move(registration_manager),
489 std::move(messaging_client));
490 }
491
~FtlSignalStrategy()492 FtlSignalStrategy::~FtlSignalStrategy() {
493 // All listeners should be removed at this point, so it's safe to detach
494 // |core_|.
495 base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
496 core_.release());
497 }
498
Connect()499 void FtlSignalStrategy::Connect() {
500 core_->Connect();
501 }
502
Disconnect()503 void FtlSignalStrategy::Disconnect() {
504 core_->Disconnect();
505 }
506
GetState() const507 SignalStrategy::State FtlSignalStrategy::GetState() const {
508 return core_->GetState();
509 }
510
GetError() const511 SignalStrategy::Error FtlSignalStrategy::GetError() const {
512 return core_->GetError();
513 }
514
GetLocalAddress() const515 const SignalingAddress& FtlSignalStrategy::GetLocalAddress() const {
516 return core_->GetLocalAddress();
517 }
518
AddListener(Listener * listener)519 void FtlSignalStrategy::AddListener(Listener* listener) {
520 core_->AddListener(listener);
521 }
522
RemoveListener(Listener * listener)523 void FtlSignalStrategy::RemoveListener(Listener* listener) {
524 core_->RemoveListener(listener);
525 }
526
SendStanza(std::unique_ptr<jingle_xmpp::XmlElement> stanza)527 bool FtlSignalStrategy::SendStanza(
528 std::unique_ptr<jingle_xmpp::XmlElement> stanza) {
529 return core_->SendStanza(std::move(stanza));
530 }
531
SendMessage(const SignalingAddress & destination_address,const ftl::ChromotingMessage & message)532 bool FtlSignalStrategy::SendMessage(const SignalingAddress& destination_address,
533 const ftl::ChromotingMessage& message) {
534 return core_->SendMessage(destination_address, message);
535 }
536
GetNextId()537 std::string FtlSignalStrategy::GetNextId() {
538 return base::NumberToString(base::RandUint64());
539 }
540
IsSignInError() const541 bool FtlSignalStrategy::IsSignInError() const {
542 return core_->IsSignInError();
543 }
544
CreateCore(std::unique_ptr<OAuthTokenGetter> oauth_token_getter,std::unique_ptr<RegistrationManager> registration_manager,std::unique_ptr<MessagingClient> messaging_client)545 void FtlSignalStrategy::CreateCore(
546 std::unique_ptr<OAuthTokenGetter> oauth_token_getter,
547 std::unique_ptr<RegistrationManager> registration_manager,
548 std::unique_ptr<MessagingClient> messaging_client) {
549 core_ = std::make_unique<Core>(std::move(oauth_token_getter),
550 std::move(registration_manager),
551 std::move(messaging_client));
552 }
553
554 } // namespace remoting
555