diff --git a/BUILD.gn b/BUILD.gn index 1be75d3d5e..3a89edcd9a 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -47,6 +47,7 @@ if (!build_with_chromium) { "call:fake_network_unittests", "common_audio:common_audio_unittests", "common_video:common_video_unittests", + "examples:examples_unittests", "media:rtc_media_unittests", "modules:modules_tests", "modules:modules_unittests", diff --git a/examples/BUILD.gn b/examples/BUILD.gn index 8d5a8f3abe..51920412b8 100644 --- a/examples/BUILD.gn +++ b/examples/BUILD.gn @@ -60,6 +60,32 @@ group("examples") { } } +rtc_source_set("read_auth_file") { + testonly = true + sources = [ + "turnserver/read_auth_file.cc", + "turnserver/read_auth_file.h", + ] + deps = [ + "../rtc_base:rtc_base", + ] +} + +if (rtc_include_tests) { + rtc_test("examples_unittests") { + testonly = true + sources = [ + "turnserver/read_auth_file_unittest.cc", + ] + deps = [ + ":read_auth_file", + "../test:test_main", + "//test:test_support", + "//testing/gtest", + ] + } +} + if (is_android) { rtc_android_apk("AppRTCMobile") { testonly = true @@ -747,6 +773,7 @@ if (is_linux || is_win) { "turnserver/turnserver_main.cc", ] deps = [ + ":read_auth_file", "../p2p:rtc_p2p", "../pc:rtc_pc", "../rtc_base:rtc_base", diff --git a/examples/turnserver/read_auth_file.cc b/examples/turnserver/read_auth_file.cc new file mode 100644 index 0000000000..adb2fd894a --- /dev/null +++ b/examples/turnserver/read_auth_file.cc @@ -0,0 +1,33 @@ +/* + * Copyright 2018 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "examples/turnserver/read_auth_file.h" +#include "rtc_base/stringencode.h" + +namespace webrtc_examples { + +std::map ReadAuthFile( + std::istream* s) { // no-presubmit-check TODO(webrtc:8982) + std::map name_to_key; + for (std::string line; std::getline(*s, line);) { + const size_t sep = line.find('='); + if (sep == std::string::npos) + continue; + char buf[32]; + size_t len = rtc::hex_decode(buf, sizeof(buf), line.data() + sep + 1, + line.size() - sep - 1); + if (len > 0) { + name_to_key.emplace(line.substr(0, sep), std::string(buf, len)); + } + } + return name_to_key; +} + +} // namespace webrtc_examples diff --git a/examples/turnserver/read_auth_file.h b/examples/turnserver/read_auth_file.h new file mode 100644 index 0000000000..4a8564e4b9 --- /dev/null +++ b/examples/turnserver/read_auth_file.h @@ -0,0 +1,25 @@ +/* + * Copyright 2018 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef EXAMPLES_TURNSERVER_READ_AUTH_FILE_H_ +#define EXAMPLES_TURNSERVER_READ_AUTH_FILE_H_ + +#include // no-presubmit-check TODO(webrtc:8982) +#include +#include + +namespace webrtc_examples { + +std::map ReadAuthFile( + std::istream* s); // no-presubmit-check TODO(webrtc:8982) + +} // namespace webrtc_examples + +#endif // EXAMPLES_TURNSERVER_READ_AUTH_FILE_H_ diff --git a/examples/turnserver/read_auth_file_unittest.cc b/examples/turnserver/read_auth_file_unittest.cc new file mode 100644 index 0000000000..de7d8ec839 --- /dev/null +++ b/examples/turnserver/read_auth_file_unittest.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2018 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include // no-presubmit-check TODO(webrtc:8982) + +#include "examples/turnserver/read_auth_file.h" +#include "test/gtest.h" + +namespace webrtc_examples { + +TEST(ReadAuthFile, HandlesEmptyFile) { + std::istringstream empty; // no-presubmit-check TODO(webrtc:8982) + auto map = ReadAuthFile(&empty); + EXPECT_TRUE(map.empty()); +} + +TEST(ReadAuthFile, RecognizesValidUser) { + std::istringstream // no-presubmit-check TODO(webrtc:8982) + file("foo=deadbeaf\n"); + auto map = ReadAuthFile(&file); + ASSERT_NE(map.find("foo"), map.end()); + EXPECT_EQ(map["foo"], "\xde\xad\xbe\xaf"); +} + +TEST(ReadAuthFile, EmptyValueForInvalidHex) { + std::istringstream file( // no-presubmit-check TODO(webrtc:8982) + "foo=deadbeaf\n" + "bar=xxxxinvalidhex\n" + "baz=cafe\n"); + auto map = ReadAuthFile(&file); + ASSERT_NE(map.find("foo"), map.end()); + EXPECT_EQ(map["foo"], "\xde\xad\xbe\xaf"); + EXPECT_EQ(map.find("bar"), map.end()); + ASSERT_NE(map.find("baz"), map.end()); + EXPECT_EQ(map["baz"], "\xca\xfe"); +} + +} // namespace webrtc_examples diff --git a/examples/turnserver/turnserver_main.cc b/examples/turnserver/turnserver_main.cc index edc0b699b4..850fc2d9c8 100644 --- a/examples/turnserver/turnserver_main.cc +++ b/examples/turnserver/turnserver_main.cc @@ -8,39 +8,44 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include // NOLINT +#include +#include +#include +#include +#include "examples/turnserver/read_auth_file.h" #include "p2p/base/basicpacketsocketfactory.h" #include "p2p/base/turnserver.h" #include "rtc_base/asyncudpsocket.h" -#include "rtc_base/optionsfile.h" #include "rtc_base/stringencode.h" #include "rtc_base/thread.h" -static const char kSoftware[] = "libjingle TurnServer"; +namespace { +const char kSoftware[] = "libjingle TurnServer"; class TurnFileAuth : public cricket::TurnAuthInterface { public: - explicit TurnFileAuth(const std::string& path) : file_(path) { file_.Load(); } + explicit TurnFileAuth(std::map name_to_key) + : name_to_key_(std::move(name_to_key)) {} + virtual bool GetKey(const std::string& username, const std::string& realm, std::string* key) { // File is stored as lines of =. // Generate HA1 via "echo -n "::" | md5sum" - std::string hex; - bool ret = file_.GetStringValue(username, &hex); - if (ret) { - char buf[32]; - size_t len = rtc::hex_decode(buf, sizeof(buf), hex); - *key = std::string(buf, len); - } - return ret; + auto it = name_to_key_.find(username); + if (it == name_to_key_.end()) + return false; + *key = it->second; + return true; } private: - rtc::OptionsFile file_; + const std::map name_to_key_; }; +} // namespace + int main(int argc, char* argv[]) { if (argc != 5) { std::cerr << "usage: turnserver int-addr ext-ip realm auth-file" @@ -70,7 +75,11 @@ int main(int argc, char* argv[]) { } cricket::TurnServer server(main); - TurnFileAuth auth(argv[4]); + std::fstream auth_file(argv[4], std::fstream::in); + + TurnFileAuth auth(auth_file.is_open() + ? webrtc_examples::ReadAuthFile(&auth_file) + : std::map()); server.set_realm(argv[3]); server.set_software(kSoftware); server.set_auth_hook(&auth); diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 86bdf2b667..8da64511a3 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -829,8 +829,6 @@ rtc_static_library("rtc_base") { "logsinks.cc", "logsinks.h", "numerics/mathutils.h", - "optionsfile.cc", - "optionsfile.h", "rollingaccumulator.h", "sslroots.h", ] @@ -1245,7 +1243,6 @@ if (rtc_include_tests) { "messagequeue_unittest.cc", "nat_unittest.cc", "network_unittest.cc", - "optionsfile_unittest.cc", "proxy_unittest.cc", "rollingaccumulator_unittest.cc", "rtccertificate_unittest.cc", diff --git a/rtc_base/optionsfile.cc b/rtc_base/optionsfile.cc deleted file mode 100644 index 075c15dda0..0000000000 --- a/rtc_base/optionsfile.cc +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2008 The WebRTC Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "rtc_base/optionsfile.h" - -#include - -#include "rtc_base/logging.h" -#include "rtc_base/stream.h" - -namespace rtc { - -OptionsFile::OptionsFile(const std::string& path) : path_(path) {} - -OptionsFile::~OptionsFile() = default; - -bool OptionsFile::Load() { - options_.clear(); - // Open file. - FileStream stream; - int err; - if (!stream.Open(path_, "r", &err)) { - RTC_LOG_F(LS_WARNING) << "Could not open file, err=" << err; - // We do not consider this an error because we expect there to be no file - // until the user saves a setting. - return true; - } - // Read in all its data. - std::string line; - StreamResult res; - for (;;) { - res = stream.ReadLine(&line); - if (res != SR_SUCCESS) { - break; - } - size_t equals_pos = line.find('='); - if (equals_pos == std::string::npos) { - // We do not consider this an error. Instead we ignore the line and - // keep going. - RTC_LOG_F(LS_WARNING) << "Ignoring malformed line in " << path_; - continue; - } - std::string key(line, 0, equals_pos); - std::string value(line, equals_pos + 1, line.length() - (equals_pos + 1)); - options_[key] = value; - } - if (res != SR_EOS) { - RTC_LOG_F(LS_ERROR) << "Error when reading from file"; - return false; - } else { - return true; - } -} - -bool OptionsFile::Save() { - // Open file. - FileStream stream; - int err; - if (!stream.Open(path_, "w", &err)) { - RTC_LOG_F(LS_ERROR) << "Could not open file, err=" << err; - return false; - } - // Write out all the data. - StreamResult res = SR_SUCCESS; - size_t written; - int error; - for (OptionsMap::const_iterator i = options_.begin(); i != options_.end(); - ++i) { - res = - stream.WriteAll(i->first.c_str(), i->first.length(), &written, &error); - if (res != SR_SUCCESS) { - break; - } - res = stream.WriteAll("=", 1, &written, &error); - if (res != SR_SUCCESS) { - break; - } - res = stream.WriteAll(i->second.c_str(), i->second.length(), &written, - &error); - if (res != SR_SUCCESS) { - break; - } - res = stream.WriteAll("\n", 1, &written, &error); - if (res != SR_SUCCESS) { - break; - } - } - if (res != SR_SUCCESS) { - RTC_LOG_F(LS_ERROR) << "Unable to write to file"; - return false; - } else { - return true; - } -} - -bool OptionsFile::IsLegalName(const std::string& name) { - for (size_t pos = 0; pos < name.length(); ++pos) { - if (name[pos] == '\n' || name[pos] == '\\' || name[pos] == '=') { - // Illegal character. - RTC_LOG(LS_WARNING) << "Ignoring operation for illegal option " << name; - return false; - } - } - return true; -} - -bool OptionsFile::IsLegalValue(const std::string& value) { - for (size_t pos = 0; pos < value.length(); ++pos) { - if (value[pos] == '\n' || value[pos] == '\\') { - // Illegal character. - RTC_LOG(LS_WARNING) << "Ignoring operation for illegal value " << value; - return false; - } - } - return true; -} - -bool OptionsFile::GetStringValue(const std::string& option, - std::string* out_val) const { - RTC_LOG(LS_VERBOSE) << "OptionsFile::GetStringValue " << option; - if (!IsLegalName(option)) { - return false; - } - OptionsMap::const_iterator i = options_.find(option); - if (i == options_.end()) { - return false; - } - *out_val = i->second; - return true; -} - -bool OptionsFile::GetIntValue(const std::string& option, int* out_val) const { - RTC_LOG(LS_VERBOSE) << "OptionsFile::GetIntValue " << option; - if (!IsLegalName(option)) { - return false; - } - OptionsMap::const_iterator i = options_.find(option); - if (i == options_.end()) { - return false; - } - return FromString(i->second, out_val); -} - -bool OptionsFile::SetStringValue(const std::string& option, - const std::string& value) { - RTC_LOG(LS_VERBOSE) << "OptionsFile::SetStringValue " << option << ":" - << value; - if (!IsLegalName(option) || !IsLegalValue(value)) { - return false; - } - options_[option] = value; - return true; -} - -bool OptionsFile::SetIntValue(const std::string& option, int value) { - RTC_LOG(LS_VERBOSE) << "OptionsFile::SetIntValue " << option << ":" << value; - if (!IsLegalName(option)) { - return false; - } - options_[option] = ToString(value); - return true; -} - -bool OptionsFile::RemoveValue(const std::string& option) { - RTC_LOG(LS_VERBOSE) << "OptionsFile::RemoveValue " << option; - if (!IsLegalName(option)) { - return false; - } - options_.erase(option); - return true; -} - -} // namespace rtc diff --git a/rtc_base/optionsfile.h b/rtc_base/optionsfile.h deleted file mode 100644 index 55660ff259..0000000000 --- a/rtc_base/optionsfile.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2008 The WebRTC Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef RTC_BASE_OPTIONSFILE_H_ -#define RTC_BASE_OPTIONSFILE_H_ - -#include -#include - -namespace rtc { - -// Implements storage of simple options in a text file on disk. This is -// cross-platform, but it is intended mostly for Linux where there is no -// first-class options storage system. -class OptionsFile { - public: - OptionsFile(const std::string& path); - ~OptionsFile(); - - // Loads the file from disk, overwriting the in-memory values. - bool Load(); - // Saves the contents in memory, overwriting the on-disk values. - bool Save(); - - bool GetStringValue(const std::string& option, std::string* out_val) const; - bool GetIntValue(const std::string& option, int* out_val) const; - bool SetStringValue(const std::string& option, const std::string& val); - bool SetIntValue(const std::string& option, int val); - bool RemoveValue(const std::string& option); - - private: - typedef std::map OptionsMap; - - static bool IsLegalName(const std::string& name); - static bool IsLegalValue(const std::string& value); - - std::string path_; - OptionsMap options_; -}; - -} // namespace rtc - -#endif // RTC_BASE_OPTIONSFILE_H_ diff --git a/rtc_base/optionsfile_unittest.cc b/rtc_base/optionsfile_unittest.cc deleted file mode 100644 index fc5bc823ff..0000000000 --- a/rtc_base/optionsfile_unittest.cc +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2008 The WebRTC Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include - -#include "rtc_base/checks.h" -#include "rtc_base/gunit.h" -#include "rtc_base/optionsfile.h" -#include "test/testsupport/fileutils.h" - -namespace rtc { - -static const std::string kTestOptionA = "test-option-a"; -static const std::string kTestOptionB = "test-option-b"; -static const std::string kTestString1 = "a string"; -static const std::string kTestString2 = "different string"; -static const std::string kOptionWithEquals = "foo=bar"; -static const std::string kOptionWithNewline = "foo\nbar"; -static const std::string kValueWithEquals = "baz=quux"; -static const std::string kValueWithNewline = "baz\nquux"; -static const std::string kEmptyString = ""; -static const char kOptionWithUtf8[] = {'O', 'p', 't', '\302', '\256', - 'i', 'o', 'n', '\342', '\204', - '\242', '\0'}; // Opt(R)io(TM). -static const char kValueWithUtf8[] = { - 'V', 'a', 'l', '\302', '\256', 'v', - 'e', '\342', '\204', '\242', '\0'}; // Val(R)ue(TM). -static int kTestInt1 = 12345; -static int kTestInt2 = 67890; -static int kNegInt = -634; -static int kZero = 0; - -#if defined(WEBRTC_ANDROID) -// Fails on Android: https://bugs.chromium.org/p/webrtc/issues/detail?id=4364. -#define MAYBE_OptionsFileTest DISABLED_OptionsFileTest -#else -#define MAYBE_OptionsFileTest OptionsFileTest -#endif - -class MAYBE_OptionsFileTest : public testing::Test { - public: - MAYBE_OptionsFileTest() { - test_file_ = - webrtc::test::TempFilename(webrtc::test::OutputPath(), ".testfile"); - OpenStore(); - } - - ~MAYBE_OptionsFileTest() override { webrtc::test::RemoveFile(test_file_); } - - protected: - void OpenStore() { store_.reset(new OptionsFile(test_file_)); } - - std::unique_ptr store_; - - private: - std::string test_file_; -}; - -TEST_F(MAYBE_OptionsFileTest, GetSetString) { - // Clear contents of the file on disk. - EXPECT_TRUE(store_->Save()); - std::string out1, out2; - EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); - EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); - EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kTestString2)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); - EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out2)); - EXPECT_EQ(kTestString1, out1); - EXPECT_EQ(kTestString2, out2); - EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_FALSE(store_->GetStringValue(kTestOptionA, &out1)); - EXPECT_FALSE(store_->GetStringValue(kTestOptionB, &out2)); -} - -TEST_F(MAYBE_OptionsFileTest, GetSetInt) { - // Clear contents of the file on disk. - EXPECT_TRUE(store_->Save()); - int out1, out2; - EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); - EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); - EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kTestInt1)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kTestInt2)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); - EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); - EXPECT_EQ(kTestInt1, out1); - EXPECT_EQ(kTestInt2, out2); - EXPECT_TRUE(store_->RemoveValue(kTestOptionA)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->RemoveValue(kTestOptionB)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_FALSE(store_->GetIntValue(kTestOptionA, &out1)); - EXPECT_FALSE(store_->GetIntValue(kTestOptionB, &out2)); - EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kNegInt)); - EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); - EXPECT_EQ(kNegInt, out1); - EXPECT_TRUE(store_->SetIntValue(kTestOptionA, kZero)); - EXPECT_TRUE(store_->GetIntValue(kTestOptionA, &out1)); - EXPECT_EQ(kZero, out1); -} - -TEST_F(MAYBE_OptionsFileTest, Persist) { - // Clear contents of the file on disk. - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); - EXPECT_TRUE(store_->SetIntValue(kTestOptionB, kNegInt)); - EXPECT_TRUE(store_->Save()); - - // Load the saved contents from above. - OpenStore(); - EXPECT_TRUE(store_->Load()); - std::string out1; - int out2; - EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out1)); - EXPECT_TRUE(store_->GetIntValue(kTestOptionB, &out2)); - EXPECT_EQ(kTestString1, out1); - EXPECT_EQ(kNegInt, out2); -} - -TEST_F(MAYBE_OptionsFileTest, SpecialCharacters) { - // Clear contents of the file on disk. - EXPECT_TRUE(store_->Save()); - std::string out; - EXPECT_FALSE(store_->SetStringValue(kOptionWithEquals, kTestString1)); - EXPECT_FALSE(store_->GetStringValue(kOptionWithEquals, &out)); - EXPECT_FALSE(store_->SetStringValue(kOptionWithNewline, kTestString1)); - EXPECT_FALSE(store_->GetStringValue(kOptionWithNewline, &out)); - EXPECT_TRUE(store_->SetStringValue(kOptionWithUtf8, kValueWithUtf8)); - EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kTestString1)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); - EXPECT_EQ(kTestString1, out); - EXPECT_TRUE(store_->GetStringValue(kOptionWithUtf8, &out)); - EXPECT_EQ(kValueWithUtf8, out); - EXPECT_FALSE(store_->SetStringValue(kTestOptionA, kValueWithNewline)); - EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); - EXPECT_EQ(kTestString1, out); - EXPECT_TRUE(store_->SetStringValue(kTestOptionA, kValueWithEquals)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->GetStringValue(kTestOptionA, &out)); - EXPECT_EQ(kValueWithEquals, out); - EXPECT_TRUE(store_->SetStringValue(kEmptyString, kTestString2)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->GetStringValue(kEmptyString, &out)); - EXPECT_EQ(kTestString2, out); - EXPECT_TRUE(store_->SetStringValue(kTestOptionB, kEmptyString)); - EXPECT_TRUE(store_->Save()); - EXPECT_TRUE(store_->Load()); - EXPECT_TRUE(store_->GetStringValue(kTestOptionB, &out)); - EXPECT_EQ(kEmptyString, out); -} - -} // namespace rtc