// Copyright (c) 2012 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 "remoting/protocol/jingle_messages.h" #include #include "base/strings/string_util.h" #include "remoting/protocol/content_description.h" #include "remoting/signaling/xmpp_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/libjingle_xmpp/xmllite/xmlelement.h" using jingle_xmpp::QName; using jingle_xmpp::XmlAttr; using jingle_xmpp::XmlElement; namespace remoting { namespace protocol { namespace { const char kXmlNsNs[] = "http://www.w3.org/2000/xmlns/"; const char kXmlNs[] = "xmlns"; // Compares two XML blobs and returns true if they are // equivalent. Otherwise |error| is set to error message that // specifies the first test. bool VerifyXml(const XmlElement* exp, const XmlElement* val, std::string* error) { if (exp->Name() != val->Name()) { *error = "<" + exp->Name().Merged() + ">" + " is expected, but " + "<" + val->Name().Merged() + ">" + " found"; return false; } if (exp->BodyText() != val->BodyText()) { *error = "<" + exp->Name().LocalPart() + ">" + exp->BodyText() + "Name().LocalPart() + ">" " is expected, but found " + "<" + exp->Name().LocalPart() + ">" + val->BodyText() + "Name().LocalPart() + ">"; return false; } for (const XmlAttr* exp_attr = exp->FirstAttr(); exp_attr != nullptr; exp_attr = exp_attr->NextAttr()) { if (exp_attr->Name().Namespace() == kXmlNsNs || exp_attr->Name() == QName(kXmlNs)) { continue; // Skip NS attributes. } if (val->Attr(exp_attr->Name()) != exp_attr->Value()) { *error = "In <" + exp->Name().LocalPart() + "> attribute " + exp_attr->Name().LocalPart() + " is expected to be set to " + exp_attr->Value(); return false; } } for (const XmlAttr* val_attr = val->FirstAttr(); val_attr; val_attr = val_attr->NextAttr()) { if (val_attr->Name().Namespace() == kXmlNsNs || val_attr->Name() == QName(kXmlNs)) { continue; // Skip NS attributes. } if (exp->Attr(val_attr->Name()) != val_attr->Value()) { *error = "In <" + exp->Name().LocalPart() + "> unexpected attribute " + val_attr->Name().LocalPart(); return false; } } const XmlElement* exp_child = exp->FirstElement(); const XmlElement* val_child = val->FirstElement(); while (exp_child && val_child) { if (!VerifyXml(exp_child, val_child, error)) return false; exp_child = exp_child->NextElement(); val_child = val_child->NextElement(); } if (exp_child) { *error = "<" + exp_child->Name().Merged() + "> is expected, but not found"; return false; } if (val_child) { *error = "Unexpected <" + val_child->Name().Merged() + "> found"; return false; } return true; } // Parses |message_text| to JingleMessage. void ParseJingleMessageFromXml(const char* message_text, JingleMessage* parsed) { std::unique_ptr source_message(XmlElement::ForStr(message_text)); ASSERT_TRUE(source_message.get()); EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get())); std::string error; EXPECT_TRUE(parsed->ParseXml(source_message.get(), &error)) << error; } // Parses |message_text| to JingleMessage then attempts to format it to XML and // verifies that the same XML content is generated. void ParseFormatAndCompare(const char* message_text, JingleMessage* parsed) { std::unique_ptr source_message(XmlElement::ForStr(message_text)); ASSERT_TRUE(source_message.get()); EXPECT_TRUE(JingleMessage::IsJingleMessage(source_message.get())); std::string error; EXPECT_TRUE(parsed->ParseXml(source_message.get(), &error)) << error; std::unique_ptr formatted_message(parsed->ToXml()); ASSERT_TRUE(formatted_message.get()); EXPECT_TRUE(VerifyXml(source_message.get(), formatted_message.get(), &error)) << error; } } // namespace // Session-initiate message for current ICE-based protocol. TEST(JingleMessageTest, SessionInitiate) { const char* kTestSessionInitiateMessage = "" "" "" "" "" "" "" "" "" "" ""; JingleMessage message; ParseFormatAndCompare(kTestSessionInitiateMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_INITIATE); EXPECT_FALSE(message.description->config()->webrtc_supported()); EXPECT_TRUE(message.description->config()->ice_supported()); } // Session-initiate message for WebRTC-based protocol. TEST(JingleMessageTest, SessionInitiateWebrtc) { const char* kTestSessionInitiateMessage = "" "" "" "" "" "j7whCMii0Z0AAPwj7whCM/j7whCMii0Z0AAPw=" "" "" "" "" "" ""; JingleMessage message; ParseFormatAndCompare(kTestSessionInitiateMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_INITIATE); EXPECT_TRUE(message.description->config()->webrtc_supported()); EXPECT_FALSE(message.description->config()->ice_supported()); } // Session-initiate message for hybrid clients that support both versions of the // protocol. TEST(JingleMessageTest, SessionInitiateHybrid) { const char* kTestSessionInitiateMessage = "" "" "" "" "" "" "" "" "" "" "" ""; JingleMessage message; ParseFormatAndCompare(kTestSessionInitiateMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_INITIATE); EXPECT_TRUE(message.description->config()->webrtc_supported()); EXPECT_TRUE(message.description->config()->ice_supported()); } // Old GICE session-initiate message from older clients that are no longer // supported. TEST(JingleMessageTest, SessionInitiateNoIce) { const char* kTestSessionInitiateMessage = "" "" "" "" "" "" "" "" "" ""; JingleMessage message; ParseJingleMessageFromXml(kTestSessionInitiateMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_INITIATE); EXPECT_FALSE(message.description->config()->ice_supported()); } // Session-accept message for current ICE-based protocol. TEST(JingleMessageTest, SessionAccept) { const char* kTestSessionAcceptMessage = "" "" "" "" "" "" "" "" "" "" ""; JingleMessage message; ParseFormatAndCompare(kTestSessionAcceptMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_ACCEPT); EXPECT_FALSE(message.description->config()->webrtc_supported()); EXPECT_TRUE(message.description->config()->ice_supported()); } // Session-accept message for WebRTC-based protocol. TEST(JingleMessageTest, SessionAcceptWebrtc) { const char* kTestSessionAcceptMessage = "" "" "" "" "" "MIICpjCCAY6gW0Cert0TANBgkqhkiG9w0BAQUFA=" "" "" "" "" "" ""; JingleMessage message; ParseFormatAndCompare(kTestSessionAcceptMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_ACCEPT); EXPECT_TRUE(message.description->config()->webrtc_supported()); EXPECT_FALSE(message.description->config()->ice_supported()); } // Old GICE session-accept message from older host that are no longer // supported. TEST(JingleMessageTest, SessionAcceptNoIce) { const char* kTestSessionAcceptMessage = "" "" "" "" "" "" "" "" "" ""; JingleMessage message; ParseJingleMessageFromXml(kTestSessionAcceptMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_ACCEPT); EXPECT_FALSE(message.description->config()->ice_supported()); EXPECT_FALSE(message.description->config()->webrtc_supported()); } TEST(JingleMessageTest, IceTransportInfo) { const char* kTestIceTransportInfoMessage = "" " " " " " " " " " " " " " " " " " " " " ""; JingleMessage message; ParseFormatAndCompare(kTestIceTransportInfoMessage, &message); EXPECT_EQ(message.action, JingleMessage::TRANSPORT_INFO); IceTransportInfo transport_info; EXPECT_TRUE(transport_info.ParseXml(message.transport_info.get())); EXPECT_EQ(transport_info.ice_credentials.size(), 2U); EXPECT_EQ(transport_info.candidates.size(), 2U); } TEST(JingleMessageTest, SessionTerminate) { const char* kTestSessionTerminateMessage = "" ""; JingleMessage message; ParseFormatAndCompare(kTestSessionTerminateMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_TERMINATE); } TEST(JingleMessageTest, SessionInfo) { const char* kTestSessionInfoMessage = "TestMessage" ""; JingleMessage message; ParseFormatAndCompare(kTestSessionInfoMessage, &message); EXPECT_EQ(message.action, JingleMessage::SESSION_INFO); ASSERT_TRUE(message.info.get() != nullptr); EXPECT_TRUE(message.info->Name() == jingle_xmpp::QName("urn:xmpp:jingle:1", "test-info")); } TEST(JingleMessageReplyTest, ToXml) { const char* kTestIncomingMessage1 = "" ""; const char* kTestIncomingMessage2 = "" ""; struct TestCase { const JingleMessageReply::ErrorType error; std::string error_text; std::string expected_text; std::string incoming_message; } tests[] = { {JingleMessageReply::BAD_REQUEST, "", "" "" "", kTestIncomingMessage1}, {JingleMessageReply::BAD_REQUEST, "ErrorText", "" "" "ErrorText", kTestIncomingMessage1}, {JingleMessageReply::NOT_IMPLEMENTED, "", "" "" "", kTestIncomingMessage1}, {JingleMessageReply::INVALID_SID, "", "" "" "Invalid SID", kTestIncomingMessage1}, {JingleMessageReply::INVALID_SID, "ErrorText", "" "" "ErrorText", kTestIncomingMessage1}, {JingleMessageReply::UNEXPECTED_REQUEST, "", "" "" "", kTestIncomingMessage1}, {JingleMessageReply::INVALID_SID, "ErrorText", "" "" "ErrorText", kTestIncomingMessage2}, {JingleMessageReply::NONE, "", "", kTestIncomingMessage2}, }; for (size_t i = 0; i < std::size(tests); ++i) { std::unique_ptr incoming_message( XmlElement::ForStr(tests[i].incoming_message)); ASSERT_TRUE(incoming_message.get()); SCOPED_TRACE(testing::Message() << "Running test case: " << i); JingleMessageReply reply_msg; if (tests[i].error_text.empty()) { reply_msg = JingleMessageReply(tests[i].error); } else { reply_msg = JingleMessageReply(tests[i].error, tests[i].error_text); } std::unique_ptr reply(reply_msg.ToXml(incoming_message.get())); std::unique_ptr expected( XmlElement::ForStr(tests[i].expected_text)); ASSERT_TRUE(expected.get()); std::string error; EXPECT_TRUE(VerifyXml(expected.get(), reply.get(), &error)) << error; } } TEST(JingleMessageTest, ErrorMessage) { const char* kTestSessionInitiateErrorMessage = "" "" "" "" "" "" "" "" "" "" "" "" ""; std::unique_ptr source_message( XmlElement::ForStr(kTestSessionInitiateErrorMessage)); ASSERT_TRUE(source_message.get()); EXPECT_FALSE(JingleMessage::IsJingleMessage(source_message.get())); JingleMessage message; std::string error; EXPECT_FALSE(message.ParseXml(source_message.get(), &error)); EXPECT_FALSE(error.empty()); } TEST(JingleMessageTest, RemotingErrorCode) { const char* kTestSessionTerminateMessageBegin = "" ""; const char* kTestSessionTerminateMessageEnd = "" ""; for (int i = OK; i <= ERROR_CODE_MAX; i++) { ErrorCode error = static_cast(i); std::string message_str = kTestSessionTerminateMessageBegin; message_str.append(ErrorCodeToString(error)); message_str.append(kTestSessionTerminateMessageEnd); JingleMessage message; if (error == UNKNOWN_ERROR) { // We do not include UNKNOWN_ERROR in xml output, so VerifyXml will fail. ParseJingleMessageFromXml(message_str.c_str(), &message); } else { ParseFormatAndCompare(message_str.c_str(), &message); } EXPECT_EQ(message.action, JingleMessage::SESSION_TERMINATE); EXPECT_EQ(message.reason, JingleMessage::DECLINE); EXPECT_EQ(message.error_code, error); } } TEST(JingleMessageTest, AttachmentsMessage) { // Ordering of the "attachments" tag and other tags are irrelevant. But the // JingleMessage implementation always puts it before other tags, so we do the // same thing in test cases. const char* kMessageWithPluginTag = "" "" "some-message" "$2"; for (int i = JingleMessage::SESSION_INITIATE; i <= JingleMessage::TRANSPORT_INFO; i++) { JingleMessage::ActionType action_type = static_cast(i); std::vector substitutes = { JingleMessage::GetActionName(action_type) }; if (action_type == JingleMessage::SESSION_INFO) { substitutes.push_back("test-message"); } else if (action_type == JingleMessage::SESSION_TERMINATE) { substitutes.emplace_back(); } else if (action_type == JingleMessage::TRANSPORT_INFO) { substitutes.push_back( "" "" "" "" "" "" "" ""); } else { substitutes.push_back("" "" "" "j7whCMii0Z0AAPwj7whCM/j7whCMii0Z0AAPw=" "" "" "" ""); } std::string message_str = base::ReplaceStringPlaceholders( kMessageWithPluginTag, substitutes, nullptr); JingleMessage message; ParseFormatAndCompare(message_str.c_str(), &message); EXPECT_TRUE(message.attachments); XmlElement expected(QName("google:remoting", "attachments")); expected.AddElement(new XmlElement(QName("google:remoting", "sometag"))); expected.FirstElement()->SetBodyText("some-message"); std::string error; EXPECT_TRUE(VerifyXml(&expected, message.attachments.get(), &error)) << error; } } } // namespace protocol } // namespace remoting