| // Copyright (c) 2023 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <poll.h> | |
| #include <unistd.h> | |
| #include <fstream> | |
| #include <iostream> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "quiche/quic/core/io/quic_event_loop.h" | |
| #include "quiche/quic/core/quic_server_id.h" | |
| #include "quiche/quic/moqt/tools/chat_client.h" | |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" | |
| #include "quiche/quic/tools/interactive_cli.h" | |
| #include "quiche/quic/tools/quic_name_lookup.h" | |
| #include "quiche/quic/tools/quic_url.h" | |
| #include "quiche/common/platform/api/quiche_command_line_flags.h" | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| bool, disable_certificate_verification, false, | |
| "If true, don't verify the server certificate."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, output_file, "", | |
| "chat messages will stream to a file instead of stdout"); | |
| using ::moqt::moq_chat::ChatClient; | |
| using ::moqt::moq_chat::ChatUserInterface; | |
| // Writes messages to a file, when directed from the command line. | |
| class FileOutput : public ChatUserInterface { | |
| public: | |
| explicit FileOutput(std::string filename, absl::string_view username) | |
| : username_(username) { | |
| output_file_.open(filename); | |
| output_file_ << "Chat transcript:\n"; | |
| output_file_.flush(); | |
| std::cout << "Fully connected. Messages are in the output file. Exit the " | |
| << "session by entering /exit\n"; | |
| } | |
| ~FileOutput() override { output_file_.close(); } | |
| void Initialize(quic::InteractiveCli::LineCallback callback, | |
| quic::QuicEventLoop* event_loop) override { | |
| callback_ = std::move(callback); | |
| event_loop_ = event_loop; | |
| } | |
| void WriteToOutput(absl::string_view user, | |
| absl::string_view message) override { | |
| if (message.empty()) { | |
| return; | |
| } | |
| output_file_ << user << ": " << message << "\n\n"; | |
| output_file_.flush(); | |
| } | |
| void IoLoop() override { | |
| std::string message_to_send; | |
| QUIC_BUG_IF(quic_bug_moq_chat_user_interface_unitialized, | |
| event_loop_ == nullptr) | |
| << "IoLoop called before Initialize"; | |
| while (poll(&poll_settings_, 1, 0) <= 0) { | |
| event_loop_->RunEventLoopOnce(moqt::moq_chat::kChatEventLoopDuration); | |
| } | |
| std::getline(std::cin, message_to_send); | |
| callback_(message_to_send); | |
| WriteToOutput(username_, message_to_send); | |
| } | |
| private: | |
| quic::QuicEventLoop* event_loop_; | |
| quic::InteractiveCli::LineCallback callback_; | |
| std::ofstream output_file_; | |
| absl::string_view username_; | |
| struct pollfd poll_settings_ = { | |
| 0, | |
| POLLIN, | |
| POLLIN, | |
| }; | |
| }; | |
| // Writes messages to the terminal, without messing up entry of new messages. | |
| class CliOutput : public ChatUserInterface { | |
| public: | |
| void Initialize(quic::InteractiveCli::LineCallback callback, | |
| quic::QuicEventLoop* event_loop) override { | |
| cli_ = | |
| std::make_unique<quic::InteractiveCli>(event_loop, std::move(callback)); | |
| event_loop_ = event_loop; | |
| cli_->PrintLine("Fully connected. Enter '/exit' to exit the chat.\n"); | |
| } | |
| void WriteToOutput(absl::string_view user, | |
| absl::string_view message) override { | |
| QUIC_BUG_IF(quic_bug_moq_chat_user_interface_unitialized, cli_ == nullptr) | |
| << "WriteToOutput called before Initialize"; | |
| cli_->PrintLine(absl::StrCat(user, ": ", message)); | |
| } | |
| void IoLoop() override { | |
| QUIC_BUG_IF(quic_bug_moq_chat_user_interface_unitialized, | |
| event_loop_ == nullptr) | |
| << "IoLoop called before Initialize"; | |
| event_loop_->RunEventLoopOnce(moqt::moq_chat::kChatEventLoopDuration); | |
| } | |
| private: | |
| quic::QuicEventLoop* event_loop_; | |
| std::unique_ptr<quic::InteractiveCli> cli_; | |
| }; | |
| // A client for MoQT over chat, used for interop testing. See | |
| // https://afrind.github.io/draft-frindell-moq-chat/draft-frindell-moq-chat.html | |
| int main(int argc, char* argv[]) { | |
| const char* usage = | |
| "Usage: chat_client [options] <url> <username> <chat-id> <localhost>"; | |
| std::vector<std::string> args = | |
| quiche::QuicheParseCommandLineFlags(usage, argc, argv); | |
| if (args.size() != 4) { | |
| quiche::QuichePrintCommandLineFlagHelp(usage); | |
| return 1; | |
| } | |
| quic::QuicUrl url(args[0], "https"); | |
| quic::QuicServerId server_id(url.host(), url.port()); | |
| std::string path = url.PathParamsQuery(); | |
| const std::string& username = args[1]; | |
| const std::string& chat_id = args[2]; | |
| std::string localhost = args[3]; | |
| std::string output_filename = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_output_file); | |
| std::unique_ptr<ChatUserInterface> interface; | |
| if (!output_filename.empty()) { | |
| interface = std::make_unique<FileOutput>(output_filename, username); | |
| } else { // Use the CLI. | |
| interface = std::make_unique<CliOutput>(); | |
| } | |
| ChatClient client( | |
| server_id, | |
| quiche::GetQuicheCommandLineFlag(FLAGS_disable_certificate_verification), | |
| std::move(interface), chat_id, username, localhost); | |
| if (!client.Connect(path)) { | |
| return 1; | |
| } | |
| if (!client.PublishNamespaceAndSubscribeNamespace()) { | |
| return 1; | |
| } | |
| client.IoLoop(); | |
| return 0; | |
| } |