blob: d209e9343fdac1b565d1b1d6f0f13439c6561f35 [file] [log] [blame]
Yuwei Huang5a84f9532019-02-20 18:29:011// 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
Yuwei Huang2687b47f2019-04-01 20:57:085#include "remoting/test/ftl_services_playground.h"
Yuwei Huangbdc4a972019-03-01 17:43:066
Yuwei Huang30bac9e2019-03-01 21:28:147#include <inttypes.h>
Yuwei Huangaae881f2019-03-01 23:10:278#include <string>
Yuwei Huangbdc4a972019-03-01 17:43:069#include <utility>
Yuwei Huang98655b92019-03-04 23:41:4110#include <vector>
Yuwei Huangbdc4a972019-03-01 17:43:0611
Yuwei Huangaae881f2019-03-01 23:10:2712#include "base/base64.h"
Yuwei Huang575c32ee2019-02-27 04:41:5613#include "base/bind.h"
Yuwei Huangbdc4a972019-03-01 17:43:0614#include "base/bind_helpers.h"
Yuwei Huang5a84f9532019-02-20 18:29:0115#include "base/command_line.h"
Yuwei Huangbdc4a972019-03-01 17:43:0616#include "base/files/file_path.h"
Yuwei Huangaae881f2019-03-01 23:10:2717#include "base/guid.h"
Yuwei Huang5a84f9532019-02-20 18:29:0118#include "base/logging.h"
Yuwei Huangbdc4a972019-03-01 17:43:0619#include "base/path_service.h"
Yuwei Huang22e0b562019-03-27 20:54:2920#include "base/run_loop.h"
Yuwei Huangb8e1f2d2019-03-22 19:40:2621#include "base/task/post_task.h"
Yuwei Huang84b92a7c2019-04-16 22:25:0022#include "remoting/base/grpc_support/grpc_async_unary_request.h"
Yuwei Huangbdc4a972019-03-01 17:43:0623#include "remoting/base/oauth_token_getter_impl.h"
Yuwei Huang8dd1b7282019-05-03 02:12:4024#include "remoting/proto/ftl/v1/ftl_services.grpc.pb.h"
Yuwei Huang82c01892019-03-28 20:39:4025#include "remoting/signaling/ftl_grpc_context.h"
Yuwei Huang41ddec52019-04-02 02:08:1926#include "remoting/test/cli_util.h"
Yuwei Huang1cf68062019-04-11 23:28:4227#include "remoting/test/test_device_id_provider.h"
Yuwei Huang41ddec52019-04-02 02:08:1928#include "remoting/test/test_oauth_token_getter.h"
Yuwei Huang946cebd42019-03-04 22:14:2029#include "remoting/test/test_token_storage.h"
Yuwei Huangbdc4a972019-03-01 17:43:0630
31namespace {
32
33constexpr char kSwitchNameHelp[] = "help";
Yuwei Huang946cebd42019-03-04 22:14:2034constexpr char kSwitchNameUsername[] = "username";
35constexpr char kSwitchNameStoragePath[] = "storage-path";
Yuwei Huangfce958772019-03-30 00:18:1136constexpr char kSwitchNameNoAutoSignin[] = "no-auto-signin";
Yuwei Huangbdc4a972019-03-01 17:43:0637
Yuwei Huangfce958772019-03-30 00:18:1138bool NeedsManualSignin() {
39 return base::CommandLine::ForCurrentProcess()->HasSwitch(
40 kSwitchNameNoAutoSignin);
Yuwei Huangb8e1f2d2019-03-22 19:40:2641}
42
Yuwei Huangbdc4a972019-03-01 17:43:0643} // namespace
Yuwei Huang5a84f9532019-02-20 18:29:0144
Yuwei Huang575c32ee2019-02-27 04:41:5645namespace remoting {
Yuwei Huang5a84f9532019-02-20 18:29:0146
Yuwei Huang2687b47f2019-04-01 20:57:0847FtlServicesPlayground::FtlServicesPlayground() : weak_factory_(this) {}
Yuwei Huang575c32ee2019-02-27 04:41:5648
Yuwei Huang2687b47f2019-04-01 20:57:0849FtlServicesPlayground::~FtlServicesPlayground() = default;
Yuwei Huang575c32ee2019-02-27 04:41:5650
Yuwei Huang2687b47f2019-04-01 20:57:0851bool FtlServicesPlayground::ShouldPrintHelp() {
Yuwei Huangbdc4a972019-03-01 17:43:0652 return base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchNameHelp);
53}
54
Yuwei Huang2687b47f2019-04-01 20:57:0855void FtlServicesPlayground::PrintHelp() {
Yuwei Huang946cebd42019-03-04 22:14:2056 printf(
Yuwei Huang41ddec52019-04-02 02:08:1957 "Usage: %s [--no-auto-signin] [--auth-code=<auth-code>] "
Yuwei Huangfce958772019-03-30 00:18:1158 "[--storage-path=<storage-path>] [--username=<[email protected]>]\n",
Yuwei Huang946cebd42019-03-04 22:14:2059 base::CommandLine::ForCurrentProcess()
60 ->GetProgram()
61 .MaybeAsASCII()
62 .c_str());
Yuwei Huangbdc4a972019-03-01 17:43:0663}
64
Yuwei Huang2687b47f2019-04-01 20:57:0865void FtlServicesPlayground::StartAndAuthenticate() {
Yuwei Huang946cebd42019-03-04 22:14:2066 DCHECK(!storage_);
Yuwei Huangbdc4a972019-03-01 17:43:0667 DCHECK(!token_getter_);
Yuwei Huang82c01892019-03-28 20:39:4068 DCHECK(!executor_);
Yuwei Huangbdc4a972019-03-01 17:43:0669
Yuwei Huang946cebd42019-03-04 22:14:2070 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
71 std::string username = cmd_line->GetSwitchValueASCII(kSwitchNameUsername);
72 base::FilePath storage_path =
73 cmd_line->GetSwitchValuePath(kSwitchNameStoragePath);
74 storage_ = test::TestTokenStorage::OnDisk(username, storage_path);
75
Yuwei Huang41ddec52019-04-02 02:08:1976 token_getter_ = std::make_unique<test::TestOAuthTokenGetter>(storage_.get());
77
Yuwei Huangfce958772019-03-30 00:18:1178 base::RunLoop run_loop;
Yuwei Huang41ddec52019-04-02 02:08:1979 token_getter_->Initialize(
80 base::BindOnce(&FtlServicesPlayground::ResetServices,
81 weak_factory_.GetWeakPtr(), run_loop.QuitClosure()));
Yuwei Huangfce958772019-03-30 00:18:1182 run_loop.Run();
Yuwei Huangaae881f2019-03-01 23:10:2783
84 StartLoop();
85}
86
Yuwei Huang2687b47f2019-04-01 20:57:0887void FtlServicesPlayground::StartLoop() {
Yuwei Huang41ddec52019-04-02 02:08:1988 std::vector<test::CommandOption> options{
89 {"GetIceServer", base::BindRepeating(&FtlServicesPlayground::GetIceServer,
90 weak_factory_.GetWeakPtr())},
91 {"PullMessages", base::BindRepeating(&FtlServicesPlayground::PullMessages,
92 weak_factory_.GetWeakPtr())},
93 {"ReceiveMessages",
94 base::BindRepeating(&FtlServicesPlayground::StartReceivingMessages,
95 weak_factory_.GetWeakPtr())},
96 {"SendMessage", base::BindRepeating(&FtlServicesPlayground::SendMessage,
97 weak_factory_.GetWeakPtr())}};
Yuwei Huangfce958772019-03-30 00:18:1198
Yuwei Huang41ddec52019-04-02 02:08:1999 if (NeedsManualSignin()) {
100 options.insert(
101 options.begin(),
102 {"SignInGaia", base::BindRepeating(&FtlServicesPlayground::SignInGaia,
103 weak_factory_.GetWeakPtr())});
Yuwei Huangaae881f2019-03-01 23:10:27104 }
Yuwei Huang41ddec52019-04-02 02:08:19105
106 test::RunCommandOptionsLoop(options);
Yuwei Huangbdc4a972019-03-01 17:43:06107}
108
Yuwei Huang2687b47f2019-04-01 20:57:08109void FtlServicesPlayground::ResetServices(base::OnceClosure on_done) {
Yuwei Huang82c01892019-03-28 20:39:40110 executor_ = std::make_unique<GrpcAuthenticatedExecutor>(token_getter_.get());
111 peer_to_peer_stub_ = PeerToPeer::NewStub(FtlGrpcContext::CreateChannel());
112
113 registration_manager_ = std::make_unique<FtlRegistrationManager>(
114 token_getter_.get(),
Yuwei Huang1cf68062019-04-11 23:28:42115 std::make_unique<test::TestDeviceIdProvider>(storage_.get()));
Yuwei Huang968994cb2019-03-21 00:55:27116
117 message_subscription_.reset();
Yuwei Huang82c01892019-03-28 20:39:40118 messaging_client_ = std::make_unique<FtlMessagingClient>(
119 token_getter_.get(), registration_manager_.get());
Yuwei Huang968994cb2019-03-21 00:55:27120 message_subscription_ = messaging_client_->RegisterMessageCallback(
Yuwei Huang2687b47f2019-04-01 20:57:08121 base::BindRepeating(&FtlServicesPlayground::OnMessageReceived,
Yuwei Huang968994cb2019-03-21 00:55:27122 weak_factory_.GetWeakPtr()));
Yuwei Huangfce958772019-03-30 00:18:11123
124 if (NeedsManualSignin()) {
125 std::move(on_done).Run();
126 } else {
127 SignInGaia(std::move(on_done));
128 }
Yuwei Huang3f1fcd362019-03-11 20:27:17129}
130
Yuwei Huang2687b47f2019-04-01 20:57:08131void FtlServicesPlayground::GetIceServer(base::OnceClosure on_done) {
Yuwei Huang3f1fcd362019-03-11 20:27:17132 DCHECK(peer_to_peer_stub_);
133 VLOG(0) << "Running GetIceServer...";
Yuwei Huang82c01892019-03-28 20:39:40134 ftl::GetICEServerRequest request;
135 *request.mutable_header() = FtlGrpcContext::CreateRequestHeader();
136 auto grpc_request = CreateGrpcAsyncUnaryRequest(
Yuwei Huang3f1fcd362019-03-11 20:27:17137 base::BindOnce(&PeerToPeer::Stub::AsyncGetICEServer,
138 base::Unretained(peer_to_peer_stub_.get())),
Yuwei Huang2071aeb2019-05-10 21:10:59139 request,
Yuwei Huang2687b47f2019-04-01 20:57:08140 base::BindOnce(&FtlServicesPlayground::OnGetIceServerResponse,
Yuwei Huang946cebd42019-03-04 22:14:20141 weak_factory_.GetWeakPtr(), std::move(on_done)));
Yuwei Huang2071aeb2019-05-10 21:10:59142 FtlGrpcContext::FillClientContext(grpc_request->context());
Yuwei Huang82c01892019-03-28 20:39:40143 executor_->ExecuteRpc(std::move(grpc_request));
Yuwei Huang575c32ee2019-02-27 04:41:56144}
145
Yuwei Huang2687b47f2019-04-01 20:57:08146void FtlServicesPlayground::OnGetIceServerResponse(
Yuwei Huang575c32ee2019-02-27 04:41:56147 base::OnceClosure on_done,
Yuwei Huang81bbc1a2019-03-08 20:46:06148 const grpc::Status& status,
Yuwei Huang575c32ee2019-02-27 04:41:56149 const ftl::GetICEServerResponse& response) {
Yuwei Huangfce958772019-03-30 00:18:11150 if (!status.ok()) {
151 HandleGrpcStatusError(std::move(on_done), status);
152 return;
153 }
154
155 printf("Ice transport policy: %s\n",
156 response.ice_config().ice_transport_policy().c_str());
157 for (const ftl::ICEServerList& server : response.ice_config().ice_servers()) {
158 printf(
159 "ICE server:\n"
160 " hostname=%s\n"
161 " username=%s\n"
162 " credential=%s\n"
163 " max_rate_kbps=%" PRId64 "\n",
164 server.hostname().c_str(), server.username().c_str(),
165 server.credential().c_str(), server.max_rate_kbps());
166 for (const std::string& url : server.urls()) {
167 printf(" url=%s\n", url.c_str());
Yuwei Huang575c32ee2019-02-27 04:41:56168 }
Yuwei Huangaae881f2019-03-01 23:10:27169 }
170 std::move(on_done).Run();
171}
172
Yuwei Huang2687b47f2019-04-01 20:57:08173void FtlServicesPlayground::SignInGaia(base::OnceClosure on_done) {
Yuwei Huange5b94452019-03-23 00:26:05174 DCHECK(registration_manager_);
Yuwei Huangaae881f2019-03-01 23:10:27175 VLOG(0) << "Running SignInGaia...";
Yuwei Huange5b94452019-03-23 00:26:05176 registration_manager_->SignInGaia(
Yuwei Huang2687b47f2019-04-01 20:57:08177 base::BindOnce(&FtlServicesPlayground::OnSignInGaiaResponse,
Yuwei Huang946cebd42019-03-04 22:14:20178 weak_factory_.GetWeakPtr(), std::move(on_done)));
Yuwei Huangaae881f2019-03-01 23:10:27179}
180
Yuwei Huang2687b47f2019-04-01 20:57:08181void FtlServicesPlayground::OnSignInGaiaResponse(base::OnceClosure on_done,
182 const grpc::Status& status) {
Yuwei Huangfce958772019-03-30 00:18:11183 if (!status.ok()) {
184 HandleGrpcStatusError(std::move(on_done), status);
185 return;
Yuwei Huang98655b92019-03-04 23:41:41186 }
Yuwei Huangfce958772019-03-30 00:18:11187
188 std::string registration_id_base64;
189 base::Base64Encode(registration_manager_->GetRegistrationId(),
190 &registration_id_base64);
191 printf("Service signed in. registration_id(base64)=%s\n",
192 registration_id_base64.c_str());
Yuwei Huang98655b92019-03-04 23:41:41193 std::move(on_done).Run();
194}
195
Yuwei Huang2687b47f2019-04-01 20:57:08196void FtlServicesPlayground::PullMessages(base::OnceClosure on_done) {
Yuwei Huang968994cb2019-03-21 00:55:27197 DCHECK(messaging_client_);
Yuwei Huang98655b92019-03-04 23:41:41198 VLOG(0) << "Running PullMessages...";
Yuwei Huang3f1fcd362019-03-11 20:27:17199
Yuwei Huang968994cb2019-03-21 00:55:27200 messaging_client_->PullMessages(
Yuwei Huang2687b47f2019-04-01 20:57:08201 base::BindOnce(&FtlServicesPlayground::OnPullMessagesResponse,
Yuwei Huang98655b92019-03-04 23:41:41202 weak_factory_.GetWeakPtr(), std::move(on_done)));
203}
204
Yuwei Huang2687b47f2019-04-01 20:57:08205void FtlServicesPlayground::OnPullMessagesResponse(base::OnceClosure on_done,
206 const grpc::Status& status) {
Yuwei Huang98655b92019-03-04 23:41:41207 if (!status.ok()) {
Yuwei Huangfce958772019-03-30 00:18:11208 HandleGrpcStatusError(std::move(on_done), status);
209 return;
Yuwei Huang5a84f9532019-02-20 18:29:01210 }
Yuwei Huang575c32ee2019-02-27 04:41:56211 std::move(on_done).Run();
212}
Yuwei Huang5a84f9532019-02-20 18:29:01213
Yuwei Huang2687b47f2019-04-01 20:57:08214void FtlServicesPlayground::SendMessage(base::OnceClosure on_done) {
Yuwei Huang4de60092019-03-22 21:19:44215 DCHECK(messaging_client_);
216 VLOG(0) << "Running SendMessage...";
217
218 printf("Receiver ID: ");
Yuwei Huang41ddec52019-04-02 02:08:19219 std::string receiver_id = test::ReadString();
Yuwei Huang4de60092019-03-22 21:19:44220
221 printf("Receiver registration ID (base64, optional): ");
Yuwei Huang41ddec52019-04-02 02:08:19222 std::string registration_id_base64 = test::ReadString();
Yuwei Huang4de60092019-03-22 21:19:44223
224 std::string registration_id;
225 bool success = base::Base64Decode(registration_id_base64, &registration_id);
226 if (!success) {
227 fprintf(stderr, "Your input can't be base64 decoded.\n");
228 std::move(on_done).Run();
229 return;
230 }
231 DoSendMessage(receiver_id, registration_id, std::move(on_done), true);
232}
233
Yuwei Huang2687b47f2019-04-01 20:57:08234void FtlServicesPlayground::DoSendMessage(const std::string& receiver_id,
235 const std::string& registration_id,
236 base::OnceClosure on_done,
237 bool should_keep_running) {
Yuwei Huang4de60092019-03-22 21:19:44238 if (!should_keep_running) {
239 std::move(on_done).Run();
240 return;
241 }
242
243 printf("Message (enter nothing to quit): ");
Yuwei Huang41ddec52019-04-02 02:08:19244 std::string message = test::ReadString();
Yuwei Huang4de60092019-03-22 21:19:44245
246 if (message.empty()) {
247 std::move(on_done).Run();
248 return;
249 }
250
Yuwei Huang2687b47f2019-04-01 20:57:08251 auto on_continue = base::BindOnce(&FtlServicesPlayground::DoSendMessage,
Yuwei Huang4de60092019-03-22 21:19:44252 weak_factory_.GetWeakPtr(), receiver_id,
253 registration_id, std::move(on_done));
254
Yuwei Huang4c919d142019-04-05 17:05:49255 ftl::ChromotingMessage crd_message;
256 crd_message.mutable_xmpp()->set_stanza(message);
Yuwei Huang4de60092019-03-22 21:19:44257 messaging_client_->SendMessage(
Yuwei Huang4c919d142019-04-05 17:05:49258 receiver_id, registration_id, crd_message,
Yuwei Huang2687b47f2019-04-01 20:57:08259 base::BindOnce(&FtlServicesPlayground::OnSendMessageResponse,
Yuwei Huang4de60092019-03-22 21:19:44260 weak_factory_.GetWeakPtr(), std::move(on_continue)));
261}
262
Yuwei Huang2687b47f2019-04-01 20:57:08263void FtlServicesPlayground::OnSendMessageResponse(
Yuwei Huang4de60092019-03-22 21:19:44264 base::OnceCallback<void(bool)> on_continue,
265 const grpc::Status& status) {
266 if (!status.ok()) {
Yuwei Huangfce958772019-03-30 00:18:11267 HandleGrpcStatusError(base::BindOnce(std::move(on_continue), false),
268 status);
269 return;
Yuwei Huang4de60092019-03-22 21:19:44270 }
Yuwei Huangfce958772019-03-30 00:18:11271
272 printf("Message successfully sent.\n");
273 std::move(on_continue).Run(true);
Yuwei Huang4de60092019-03-22 21:19:44274}
275
Yuwei Huang2687b47f2019-04-01 20:57:08276void FtlServicesPlayground::StartReceivingMessages(base::OnceClosure on_done) {
Yuwei Huangb8e1f2d2019-03-22 19:40:26277 VLOG(0) << "Running StartReceivingMessages...";
Yuwei Huangce344e32019-04-05 19:31:29278 receive_messages_done_callback_ = std::move(on_done);
Yuwei Huangb8e1f2d2019-03-22 19:40:26279 messaging_client_->StartReceivingMessages(
Yuwei Huangce344e32019-04-05 19:31:29280 base::BindOnce(&FtlServicesPlayground::OnReceiveMessagesStreamReady,
281 weak_factory_.GetWeakPtr()),
282 base::BindOnce(&FtlServicesPlayground::OnReceiveMessagesStreamClosed,
283 weak_factory_.GetWeakPtr()));
Yuwei Huangb8e1f2d2019-03-22 19:40:26284}
285
Yuwei Huang2687b47f2019-04-01 20:57:08286void FtlServicesPlayground::StopReceivingMessages(base::OnceClosure on_done) {
Yuwei Huangb8e1f2d2019-03-22 19:40:26287 messaging_client_->StopReceivingMessages();
288 std::move(on_done).Run();
289}
290
Yuwei Huangc9bb46f2019-04-04 02:19:00291void FtlServicesPlayground::OnMessageReceived(
Yuwei Huang4ef50e732019-05-30 04:30:25292 const ftl::Id& sender_id,
Yuwei Huangc9bb46f2019-04-04 02:19:00293 const std::string& sender_registration_id,
Yuwei Huang4c919d142019-04-05 17:05:49294 const ftl::ChromotingMessage& message) {
295 std::string message_text = message.xmpp().stanza();
Yuwei Huang968994cb2019-03-21 00:55:27296 printf(
297 "Received message:\n"
298 " Sender ID=%s\n"
Yuwei Huangc9bb46f2019-04-04 02:19:00299 " Sender Registration ID=%s\n"
Yuwei Huang968994cb2019-03-21 00:55:27300 " Message=%s\n",
Yuwei Huang4ef50e732019-05-30 04:30:25301 sender_id.id().c_str(), sender_registration_id.c_str(),
302 message_text.c_str());
Yuwei Huang968994cb2019-03-21 00:55:27303}
304
Yuwei Huangce344e32019-04-05 19:31:29305void FtlServicesPlayground::OnReceiveMessagesStreamReady() {
306 printf("Started receiving messages. Press enter to stop streaming...\n");
307 test::WaitForEnterKey(base::BindOnce(
308 &FtlServicesPlayground::StopReceivingMessages, weak_factory_.GetWeakPtr(),
309 std::move(receive_messages_done_callback_)));
310}
311
312void FtlServicesPlayground::OnReceiveMessagesStreamClosed(
Yuwei Huangb8e1f2d2019-03-22 19:40:26313 const grpc::Status& status) {
Yuwei Huangce344e32019-04-05 19:31:29314 base::OnceClosure callback = std::move(receive_messages_done_callback_);
315 bool is_callback_null = callback.is_null();
316 if (is_callback_null) {
317 callback = base::DoNothing::Once();
318 }
Yuwei Huangb8e1f2d2019-03-22 19:40:26319 if (status.error_code() == grpc::StatusCode::CANCELLED) {
320 printf("ReceiveMessages stream canceled by client.\n");
Yuwei Huangce344e32019-04-05 19:31:29321 std::move(callback).Run();
Yuwei Huangb8e1f2d2019-03-22 19:40:26322 return;
323 }
Yuwei Huangfce958772019-03-30 00:18:11324
Yuwei Huangb8e1f2d2019-03-22 19:40:26325 if (!status.ok()) {
Yuwei Huangce344e32019-04-05 19:31:29326 HandleGrpcStatusError(std::move(callback), status);
327 } else {
328 printf("Stream closed by server.\n");
329 std::move(callback).Run();
Yuwei Huangb8e1f2d2019-03-22 19:40:26330 }
Yuwei Huangce344e32019-04-05 19:31:29331
332 if (is_callback_null) {
333 // Stream had been started and callback has been passed to wait for the
334 // enter key.
335 printf("Please press enter to continue...\n");
336 }
Yuwei Huangb8e1f2d2019-03-22 19:40:26337}
338
Yuwei Huang2687b47f2019-04-01 20:57:08339void FtlServicesPlayground::HandleGrpcStatusError(base::OnceClosure on_done,
340 const grpc::Status& status) {
Yuwei Huangfce958772019-03-30 00:18:11341 DCHECK(!status.ok());
342 if (status.error_code() == grpc::StatusCode::UNAUTHENTICATED) {
343 if (NeedsManualSignin()) {
344 printf(
345 "Request is unauthenticated. You should run SignInGaia first if "
346 "you haven't done so, otherwise your OAuth token might be expired. \n"
347 "Request for new OAuth token? [y/N]: ");
Yuwei Huangdc86bdf42019-04-18 19:14:10348 if (!test::ReadYNBool()) {
Yuwei Huangfce958772019-03-30 00:18:11349 std::move(on_done).Run();
350 return;
351 }
352 }
353 VLOG(0) << "Grpc request failed to authenticate. "
354 << "Trying to reauthenticate...";
Yuwei Huang41ddec52019-04-02 02:08:19355 token_getter_->ResetWithAuthenticationFlow(
356 base::BindOnce(&FtlServicesPlayground::ResetServices,
357 weak_factory_.GetWeakPtr(), std::move(on_done)));
Yuwei Huangfce958772019-03-30 00:18:11358 return;
359 }
360
361 fprintf(stderr, "RPC failed. Code=%d, Message=%s\n", status.error_code(),
362 status.error_message().c_str());
363 std::move(on_done).Run();
364}
365
Yuwei Huang575c32ee2019-02-27 04:41:56366} // namespace remoting