blob: 6ed29616e7f57afa5cf67f00d90babc97bfa9ec8 [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 Huangbdc4a972019-03-01 17:43:065#include "remoting/test/ftl_signaling_playground.h"
6
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 Huangaae881f2019-03-01 23:10:2720#include "base/strings/string_number_conversions.h"
Yuwei Huangbdc4a972019-03-01 17:43:0621#include "base/strings/stringprintf.h"
Yuwei Huang946cebd42019-03-04 22:14:2022#include "remoting/base/fake_oauth_token_getter.h"
Yuwei Huangbdc4a972019-03-01 17:43:0623#include "remoting/base/oauth_token_getter_impl.h"
Yuwei Huang5a84f9532019-02-20 18:29:0124#include "remoting/signaling/ftl_client.h"
Yuwei Huangbdc4a972019-03-01 17:43:0625#include "remoting/test/test_oauth_token_factory.h"
Yuwei Huang946cebd42019-03-04 22:14:2026#include "remoting/test/test_token_storage.h"
Yuwei Huangbdc4a972019-03-01 17:43:0627
28namespace {
29
30constexpr char kSwitchNameHelp[] = "help";
31constexpr char kSwitchNameAuthCode[] = "code";
Yuwei Huang946cebd42019-03-04 22:14:2032constexpr char kSwitchNameUsername[] = "username";
33constexpr char kSwitchNameStoragePath[] = "storage-path";
Yuwei Huangbdc4a972019-03-01 17:43:0634
35// Reads a newline-terminated string from stdin.
36std::string ReadString() {
37 const int kMaxLen = 1024;
38 std::string str(kMaxLen, 0);
39 char* result = fgets(&str[0], kMaxLen, stdin);
40 if (!result)
41 return std::string();
42 size_t newline_index = str.find('\n');
43 if (newline_index != std::string::npos)
44 str[newline_index] = '\0';
45 str.resize(strlen(&str[0]));
46 return str;
47}
48
49// Read the value of |switch_name| from command line if it exists, otherwise
50// read from stdin.
51std::string ReadStringFromCommandLineOrStdin(const std::string& switch_name,
52 const std::string& read_prompt) {
53 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
54 if (command_line->HasSwitch(switch_name)) {
55 return command_line->GetSwitchValueASCII(switch_name);
56 }
57 printf("%s", read_prompt.c_str());
58 return ReadString();
59}
60
61} // namespace
Yuwei Huang5a84f9532019-02-20 18:29:0162
Yuwei Huang575c32ee2019-02-27 04:41:5663namespace remoting {
Yuwei Huang5a84f9532019-02-20 18:29:0164
Yuwei Huang946cebd42019-03-04 22:14:2065FtlSignalingPlayground::FtlSignalingPlayground() : weak_factory_(this) {}
Yuwei Huang575c32ee2019-02-27 04:41:5666
67FtlSignalingPlayground::~FtlSignalingPlayground() = default;
68
Yuwei Huangbdc4a972019-03-01 17:43:0669bool FtlSignalingPlayground::ShouldPrintHelp() {
70 return base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchNameHelp);
71}
72
73void FtlSignalingPlayground::PrintHelp() {
Yuwei Huang946cebd42019-03-04 22:14:2074 printf(
75 "Usage: %s [--code=<auth-code>] [--storage-path=<storage-path>] "
76 "[--username=<[email protected]>]\n",
77 base::CommandLine::ForCurrentProcess()
78 ->GetProgram()
79 .MaybeAsASCII()
80 .c_str());
Yuwei Huangbdc4a972019-03-01 17:43:0681}
82
83void FtlSignalingPlayground::StartAndAuthenticate() {
Yuwei Huang946cebd42019-03-04 22:14:2084 DCHECK(!storage_);
Yuwei Huangbdc4a972019-03-01 17:43:0685 DCHECK(!token_getter_factory_);
86 DCHECK(!token_getter_);
87 DCHECK(!client_);
88
Yuwei Huangbdc4a972019-03-01 17:43:0689 token_getter_factory_ = std::make_unique<TestOAuthTokenGetterFactory>();
90
Yuwei Huang946cebd42019-03-04 22:14:2091 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
92 std::string username = cmd_line->GetSwitchValueASCII(kSwitchNameUsername);
93 base::FilePath storage_path =
94 cmd_line->GetSwitchValuePath(kSwitchNameStoragePath);
95 storage_ = test::TestTokenStorage::OnDisk(username, storage_path);
96
97 std::string access_token = storage_->FetchAccessToken();
98 if (access_token.empty()) {
99 AuthenticateAndResetClient();
100 } else {
101 VLOG(0) << "Reusing access token: " << access_token;
102 token_getter_ = std::make_unique<FakeOAuthTokenGetter>(
103 OAuthTokenGetter::Status::SUCCESS, "[email protected]",
104 access_token);
105 client_ = std::make_unique<FtlClient>(token_getter_.get());
106 }
Yuwei Huangaae881f2019-03-01 23:10:27107
108 StartLoop();
109}
110
111void FtlSignalingPlayground::StartLoop() {
112 while (true) {
113 printf(
114 "\nOptions:\n"
115 " 1. GetIceServer\n"
116 " 2. SignInGaia\n"
Yuwei Huang98655b92019-03-04 23:41:41117 " 3. PullMessages\n"
118 " 4. Quit\n\n"
Yuwei Huangaae881f2019-03-01 23:10:27119 "Your choice [number]: ");
120 int choice = 0;
121 base::StringToInt(ReadString(), &choice);
122 base::RunLoop run_loop;
123 switch (choice) {
124 case 1:
125 GetIceServer(run_loop.QuitClosure());
126 break;
127 case 2:
128 SignInGaia(run_loop.QuitClosure());
129 break;
130 case 3:
Yuwei Huang98655b92019-03-04 23:41:41131 PullMessages(run_loop.QuitClosure());
132 break;
133 case 4:
Yuwei Huangaae881f2019-03-01 23:10:27134 return;
135 default:
136 fprintf(stderr, "Unknown option\n");
137 continue;
138 }
139 run_loop.Run();
140 }
Yuwei Huangbdc4a972019-03-01 17:43:06141}
142
Yuwei Huang946cebd42019-03-04 22:14:20143void FtlSignalingPlayground::AuthenticateAndResetClient() {
144 static const std::string read_auth_code_prompt = base::StringPrintf(
145 "Please authenticate at:\n\n"
146 " %s\n\n"
147 "Enter the auth code: ",
148 TestOAuthTokenGetterFactory::GetAuthorizationCodeUri().c_str());
149 std::string auth_code = ReadStringFromCommandLineOrStdin(
150 kSwitchNameAuthCode, read_auth_code_prompt);
151
152 // Make sure we don't try to reuse an auth code.
153 base::CommandLine::ForCurrentProcess()->RemoveSwitch(kSwitchNameAuthCode);
154
155 // We can't get back the refresh token since we have first-party scope, so
156 // we are not trying to store it.
157 token_getter_ = token_getter_factory_->CreateFromIntermediateCredentials(
158 auth_code,
159 base::DoNothing::Repeatedly<const std::string&, const std::string&>());
160
161 // Get the access token so that we can reuse it for next time.
162 base::OnceClosure on_access_token_done = base::DoNothing::Once();
163 base::RunLoop run_loop;
164 if (!base::RunLoop::IsRunningOnCurrentThread()) {
165 on_access_token_done = run_loop.QuitClosure();
166 }
167 token_getter_->CallWithToken(base::BindOnce(
168 &FtlSignalingPlayground::OnAccessToken, weak_factory_.GetWeakPtr(),
169 std::move(on_access_token_done)));
170 if (!base::RunLoop::IsRunningOnCurrentThread()) {
171 run_loop.Run();
172 }
173
174 client_ = std::make_unique<FtlClient>(token_getter_.get());
175}
176
177void FtlSignalingPlayground::OnAccessToken(base::OnceClosure on_done,
178 OAuthTokenGetter::Status status,
179 const std::string& user_email,
180 const std::string& access_token) {
181 DCHECK(status == OAuthTokenGetter::Status::SUCCESS);
182 VLOG(0) << "Received access_token: " << access_token;
183 storage_->StoreAccessToken(access_token);
184 std::move(on_done).Run();
185}
186
187void FtlSignalingPlayground::HandleGrpcStatusError(const grpc::Status& status) {
188 DCHECK(!status.ok());
189 LOG(ERROR) << "RPC failed. Code=" << status.error_code() << ", "
190 << "Message=" << status.error_message();
191 switch (status.error_code()) {
192 case grpc::StatusCode::UNAVAILABLE:
193 VLOG(0)
194 << "Set the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable "
195 << "to third_party/grpc/src/etc/roots.pem if gRPC cannot locate the "
196 << "root certificates.";
197 break;
198 case grpc::StatusCode::UNAUTHENTICATED:
199 VLOG(0) << "Grpc request failed to authenticate. "
200 << "Trying to reauthenticate...";
201 AuthenticateAndResetClient();
202 break;
203 default:
204 break;
205 }
206}
207
Yuwei Huang575c32ee2019-02-27 04:41:56208void FtlSignalingPlayground::GetIceServer(base::OnceClosure on_done) {
Yuwei Huangbdc4a972019-03-01 17:43:06209 DCHECK(client_);
Yuwei Huang946cebd42019-03-04 22:14:20210 client_->GetIceServer(
211 base::BindOnce(&FtlSignalingPlayground::OnGetIceServerResponse,
212 weak_factory_.GetWeakPtr(), std::move(on_done)));
Yuwei Huang575c32ee2019-02-27 04:41:56213 VLOG(0) << "Running GetIceServer...";
214}
215
Yuwei Huang575c32ee2019-02-27 04:41:56216void FtlSignalingPlayground::OnGetIceServerResponse(
217 base::OnceClosure on_done,
218 grpc::Status status,
219 const ftl::GetICEServerResponse& response) {
220 if (status.ok()) {
Yuwei Huang30bac9e2019-03-01 21:28:14221 printf("Ice transport policy: %s\n",
222 response.ice_config().ice_transport_policy().c_str());
Yuwei Huang575c32ee2019-02-27 04:41:56223 for (const ftl::ICEServerList& server :
224 response.ice_config().ice_servers()) {
Yuwei Huang30bac9e2019-03-01 21:28:14225 printf(
226 "ICE server:\n"
227 " hostname=%s\n"
228 " username=%s\n"
229 " credential=%s\n"
230 " max_rate_kbps=%" PRId64 "\n",
231 server.hostname().c_str(), server.username().c_str(),
232 server.credential().c_str(), server.max_rate_kbps());
Yuwei Huang575c32ee2019-02-27 04:41:56233 for (const std::string& url : server.urls()) {
Yuwei Huang30bac9e2019-03-01 21:28:14234 printf(" url=%s\n", url.c_str());
Yuwei Huang575c32ee2019-02-27 04:41:56235 }
236 }
237 } else {
Yuwei Huang946cebd42019-03-04 22:14:20238 HandleGrpcStatusError(status);
Yuwei Huangaae881f2019-03-01 23:10:27239 }
240 std::move(on_done).Run();
241}
242
243void FtlSignalingPlayground::SignInGaia(base::OnceClosure on_done) {
244 DCHECK(client_);
245 VLOG(0) << "Running SignInGaia...";
Yuwei Huang946cebd42019-03-04 22:14:20246 // TODO(yuweih): Logic should be cleaned up and moved out of the playground.
247 std::string device_id = storage_->FetchDeviceId();
248 if (device_id.empty()) {
249 device_id = "crd-web-" + base::GenerateGUID();
250 VLOG(0) << "Generated new device_id: " << device_id;
251 storage_->StoreDeviceId(device_id);
252 } else {
253 VLOG(0) << "Read device_id: " << device_id;
254 }
Yuwei Huangaae881f2019-03-01 23:10:27255 VLOG(0) << "Using sign_in_gaia_mode: DEFAULT_CREATE_ACCOUNT";
256 client_->SignInGaia(
257 device_id, ftl::SignInGaiaMode_Value_DEFAULT_CREATE_ACCOUNT,
258 base::BindOnce(&FtlSignalingPlayground::OnSignInGaiaResponse,
Yuwei Huang946cebd42019-03-04 22:14:20259 weak_factory_.GetWeakPtr(), std::move(on_done)));
Yuwei Huangaae881f2019-03-01 23:10:27260}
261
Yuwei Huangaae881f2019-03-01 23:10:27262void FtlSignalingPlayground::OnSignInGaiaResponse(
263 base::OnceClosure on_done,
264 grpc::Status status,
265 const ftl::SignInGaiaResponse& response) {
266 if (status.ok()) {
267 // TODO(yuweih): Allow loading auth token directly from command line.
268 std::string registration_id_base64;
269 std::string auth_token_base64;
270 base::Base64Encode(response.registration_id(), &registration_id_base64);
271 base::Base64Encode(response.auth_token().payload(), &auth_token_base64);
272 printf(
273 "registration_id(base64)=%s\n"
274 "auth_token.payload(base64)=%s\n"
275 "auth_token.expires_in=%" PRId64 "\n",
276 registration_id_base64.c_str(), auth_token_base64.c_str(),
277 response.auth_token().expires_in());
Yuwei Huang98655b92019-03-04 23:41:41278 client_->SetAuthToken(response.auth_token().payload());
279 VLOG(0) << "Auth token set on FtlClient";
280 } else {
281 HandleGrpcStatusError(status);
282 }
283 std::move(on_done).Run();
284}
285
286void FtlSignalingPlayground::PullMessages(base::OnceClosure on_done) {
287 DCHECK(client_);
288 VLOG(0) << "Running PullMessages...";
289 client_->PullMessages(
290 base::BindOnce(&FtlSignalingPlayground::OnPullMessagesResponse,
291 weak_factory_.GetWeakPtr(), std::move(on_done)));
292}
293
294void FtlSignalingPlayground::OnPullMessagesResponse(
295 base::OnceClosure on_done,
296 grpc::Status status,
297 const ftl::PullMessagesResponse& response) {
298 if (!status.ok()) {
299 if (status.error_code() == grpc::StatusCode::UNAUTHENTICATED) {
300 fprintf(stderr, "Please run SignInGaia first\n");
301 } else {
302 HandleGrpcStatusError(status);
303 }
304 std::move(on_done).Run();
305 return;
306 }
307
308 std::vector<ftl::ReceiverMessage> receiver_messages;
309 printf("pull_all=%d\n", response.pulled_all());
310 for (const auto& message : response.messages()) {
311 printf(
312 "Message:\n"
313 " message_type=%d\n"
314 " message_id=%s\n"
315 " sender_id.id=%s\n"
316 " receiver_id.id=%s\n",
317 message.message_type(), message.message_id().c_str(),
318 message.sender_id().id().c_str(), message.receiver_id().id().c_str());
319
320 if (message.message_type() ==
321 ftl::InboxMessage_MessageType_CHROMOTING_MESSAGE) {
322 ftl::ChromotingMessage chromoting_message;
323 chromoting_message.ParseFromString(message.message());
324 printf(" message(ChromotingMessage deserialized)=%s\n",
325 chromoting_message.message().c_str());
326 } else {
327 std::string message_base64;
328 base::Base64Encode(message.message(), &message_base64);
329 printf(" message(base64)=%s\n", message_base64.c_str());
330 }
331
332 ftl::ReceiverMessage receiver_message;
333 receiver_message.set_message_id(message.message_id());
334 receiver_message.set_allocated_receiver_id(
335 new ftl::Id(message.receiver_id()));
336 receiver_messages.push_back(std::move(receiver_message));
337 }
338
339 if (receiver_messages.empty()) {
340 VLOG(0) << "No message has been received";
341 std::move(on_done).Run();
342 return;
343 }
344
345 // TODO(yuweih): Might need retry logic.
346 VLOG(0) << "Acking " << receiver_messages.size() << " messages";
347 client_->AckMessages(
348 receiver_messages,
349 base::BindOnce(&FtlSignalingPlayground::OnAckMessagesResponse,
350 weak_factory_.GetWeakPtr(), std::move(on_done)));
351}
352
353void FtlSignalingPlayground::OnAckMessagesResponse(
354 base::OnceClosure on_done,
355 grpc::Status status,
356 const ftl::AckMessagesResponse& response) {
357 if (status.ok()) {
358 VLOG(0) << "Messages acked";
Yuwei Huangaae881f2019-03-01 23:10:27359 } else {
Yuwei Huang946cebd42019-03-04 22:14:20360 HandleGrpcStatusError(status);
Yuwei Huang5a84f9532019-02-20 18:29:01361 }
Yuwei Huang575c32ee2019-02-27 04:41:56362 std::move(on_done).Run();
363}
Yuwei Huang5a84f9532019-02-20 18:29:01364
Yuwei Huang575c32ee2019-02-27 04:41:56365} // namespace remoting