[go: nahoru, domu]

blob: b51afbc133b65303e0bdcc888ecf0067bc7fad90 [file] [log] [blame]
// Copyright 2016 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 "extensions/browser/api/display_source/display_source_apitestbase.h"
#include <list>
#include <map>
#include <utility>
#include "base/memory/ptr_util.h"
#include "net/base/net_errors.h"
#include "net/udp/udp_socket.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
using api::display_source::SinkInfo;
using api::display_source::SinkState;
using api::display_source::AuthenticationMethod;
using api::display_source::SINK_STATE_DISCONNECTED;
using api::display_source::SINK_STATE_CONNECTING;
using api::display_source::SINK_STATE_CONNECTED;
using api::display_source::AUTHENTICATION_METHOD_PBC;
using api::display_source::AUTHENTICATION_METHOD_PIN;
using content::BrowserThread;
class MockDisplaySourceConnectionDelegate
: public DisplaySourceConnectionDelegate,
public DisplaySourceConnectionDelegate::Connection {
public:
MockDisplaySourceConnectionDelegate();
const DisplaySourceSinkInfoList& last_found_sinks() const override;
DisplaySourceConnectionDelegate::Connection* connection()
override {
return (active_sink_ && active_sink_->state == SINK_STATE_CONNECTED)
? this
: nullptr;
}
void GetAvailableSinks(const SinkInfoListCallback& sinks_callback,
const StringCallback& failure_callback) override;
void RequestAuthentication(int sink_id,
const AuthInfoCallback& auth_info_callback,
const StringCallback& failure_callback) override;
void Connect(int sink_id,
const DisplaySourceAuthInfo& auth_info,
const StringCallback& failure_callback) override;
void Disconnect(const StringCallback& failure_callback) override;
void StartWatchingAvailableSinks() override;
// DisplaySourceConnectionDelegate::Connection overrides
const DisplaySourceSinkInfo& GetConnectedSink() const override;
void StopWatchingAvailableSinks() override;
std::string GetLocalAddress() const override;
std::string GetSinkAddress() const override;
void SendMessage(const std::string& message) override;
void SetMessageReceivedCallback(
const StringCallback& callback) override;
private:
void AddSink(DisplaySourceSinkInfo sink,
AuthenticationMethod auth_method,
const std::string& pin_value);
void OnSinkConnected();
void NotifySinksUpdated();
void EnqueueSinkMessage(std::string message);
void CheckSourceMessageContent(std::string pattern,
const std::string& message);
void BindToUdpSocket();
void ReceiveMediaPacket();
void OnMediaPacketReceived(int net_result);
DisplaySourceSinkInfoList sinks_;
DisplaySourceSinkInfo* active_sink_;
std::map<int, std::pair<AuthenticationMethod, std::string>> auth_infos_;
StringCallback message_received_cb_;
struct Message {
enum Direction {
SourceToSink,
SinkToSource
};
std::string data;
Direction direction;
bool is_from_sink() const { return direction == SinkToSource; }
Message(const std::string& message_data, Direction direction)
: data(message_data), direction(direction) {}
};
std::list<Message> messages_list_;
std::string session_id_;
std::unique_ptr<net::UDPSocket,
content::BrowserThread::DeleteOnIOThread> socket_;
scoped_refptr<net::IOBuffer> recvfrom_buffer_;
net::IPEndPoint end_point_;
std::string udp_port_;
};
namespace {
const size_t kSessionIdLength = 8;
const size_t kUdpPortLength = 5;
const char kClientPortKey[] = "client_port=";
const char kLocalHost[] = "127.0.0.1";
const char kSessionKey[] = "Session: ";
const char kUnicastKey[] = "unicast ";
const int kPortStart = 10000;
const int kPortEnd = 65535;
DisplaySourceSinkInfo CreateSinkInfo(int id, const std::string& name) {
DisplaySourceSinkInfo ptr;
ptr.id = id;
ptr.name = name;
ptr.state = SINK_STATE_DISCONNECTED;
return ptr;
}
std::unique_ptr<KeyedService> CreateMockDelegate(
content::BrowserContext* profile) {
return base::WrapUnique<KeyedService>(
new MockDisplaySourceConnectionDelegate());
}
void AdaptMessagePattern(std::size_t key_pos,
const char *key,
std::size_t substr_len,
const std::string& replace_with,
std::string& pattern) {
const std::size_t position = key_pos +
std::char_traits<char>::length(key);
pattern.replace(position, substr_len, replace_with);
}
} // namespace
void InitMockDisplaySourceConnectionDelegate(content::BrowserContext* profile) {
DisplaySourceConnectionDelegateFactory::GetInstance()->SetTestingFactory(
profile, &CreateMockDelegate);
}
namespace {
// WiFi Display session RTSP messages patterns.
const char kM1Message[] = "OPTIONS * RTSP/1.0\r\n"
"CSeq: 1\r\n"
"Require: org.wfa.wfd1.0\r\n\r\n";
const char kM1MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq:1\r\n"
"Public: org.wfa.wfd1.0, "
"GET_PARAMETER, SET_PARAMETER\r\n\r\n";
const char kM2Message[] = "OPTIONS * RTSP/1.0\r\n"
"CSeq: 2\r\n"
"Require: org.wfa.wfd1.0\r\n\r\n";
const char kM2MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 2\r\n"
"Public: org.wfa.wfd1.0, "
"GET_PARAMETER, SET_PARAMETER, PLAY, PAUSE, "
"SETUP, TEARDOWN\r\n\r\n";
const char kM3Message[] = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"
"CSeq: 2\r\n"
"Content-Type: text/parameters\r\n"
"Content-Length: 41\r\n\r\n"
"wfd_video_formats\r\n"
"wfd_client_rtp_ports\r\n";
const char kM3MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 2\r\n"
"Content-Type: text/parameters\r\n"
"Content-Length: 145\r\n\r\n"
"wfd_video_formats: "
"00 00 01 01 0001FFFF 1FFFFFFF 00000FFF 00 0000 "
"0000 00 none none\r\n"
"wfd_client_rtp_ports: RTP/AVP/UDP;"
"unicast 00000 0 mode=play\r\n";
const char kM4Message[] = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"
"CSeq: 3\r\n"
"Content-Type: text/parameters\r\n"
"Content-Length: 209\r\n\r\n"
"wfd_client_rtp_ports: "
"RTP/AVP/UDP;unicast 00000 0 mode=play\r\n"
"wfd_presentation_URL: "
"rtsp://127.0.0.1/wfd1.0/streamid=0 none\r\n"
"wfd_video_formats: "
"00 00 01 01 00000001 00000000 00000000 00 0000 0000 "
"00 none none\r\n";
const char kM4MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 3\r\n\r\n";
const char kM5Message[] = "SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"
"CSeq: 4\r\n"
"Content-Type: text/parameters\r\n"
"Content-Length: 27\r\n\r\n"
"wfd_trigger_method: SETUP\r\n";
const char kM5MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 4\r\n\r\n";
const char kM6Message[] = "SETUP rtsp://localhost/wfd1.0/streamid=0 "
"RTSP/1.0\r\n"
"CSeq: 3\r\n"
"Transport: RTP/AVP/UDP;unicast;"
"client_port=00000\r\n\r\n";
const char kM6MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 3\r\n"
"Session: 00000000;timeout=60\r\n"
"Transport: RTP/AVP/UDP;unicast;"
"client_port=00000\r\n\r\n";
const char kM7Message[] = "PLAY rtsp://localhost/wfd1.0/streamid=0 RTSP/1.0\r\n"
"CSeq: 4\r\n"
"Session: 00000000\r\n\r\n";
const char kM7MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 4\r\n\r\n";
const char kM8Message[] = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"
"CSeq: 5\r\n\r\n";
const char kM8MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 5\r\n\r\n";
const char kM9Message[] = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"
"CSeq: 6\r\n\r\n";
const char kM9MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 6\r\n\r\n";
const char kM10Message[] = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n"
"CSeq: 7\r\n\r\n";
const char kM10MessageReply[] = "RTSP/1.0 200 OK\r\n"
"CSeq: 7\r\n\r\n";
} // namespace
MockDisplaySourceConnectionDelegate::MockDisplaySourceConnectionDelegate()
: active_sink_(nullptr) {
messages_list_.push_back(Message(kM1Message, Message::SourceToSink));
messages_list_.push_back(Message(kM1MessageReply, Message::SinkToSource));
messages_list_.push_back(Message(kM2Message, Message::SinkToSource));
messages_list_.push_back(Message(kM2MessageReply, Message::SourceToSink));
messages_list_.push_back(Message(kM3Message, Message::SourceToSink));
messages_list_.push_back(Message(kM3MessageReply, Message::SinkToSource));
messages_list_.push_back(Message(kM4Message, Message::SourceToSink));
messages_list_.push_back(Message(kM4MessageReply, Message::SinkToSource));
messages_list_.push_back(Message(kM5Message, Message::SourceToSink));
messages_list_.push_back(Message(kM5MessageReply, Message::SinkToSource));
messages_list_.push_back(Message(kM6Message, Message::SinkToSource));
messages_list_.push_back(Message(kM6MessageReply, Message::SourceToSink));
messages_list_.push_back(Message(kM7Message, Message::SinkToSource));
messages_list_.push_back(Message(kM7MessageReply, Message::SourceToSink));
messages_list_.push_back(Message(kM8Message, Message::SourceToSink));
messages_list_.push_back(Message(kM8MessageReply, Message::SinkToSource));
messages_list_.push_back(Message(kM9Message, Message::SourceToSink));
messages_list_.push_back(Message(kM9MessageReply, Message::SinkToSource));
messages_list_.push_back(Message(kM10Message, Message::SourceToSink));
messages_list_.push_back(Message(kM10MessageReply, Message::SinkToSource));
AddSink(CreateSinkInfo(1, "sink 1"), AUTHENTICATION_METHOD_PIN, "1234");
}
const DisplaySourceSinkInfoList&
MockDisplaySourceConnectionDelegate::last_found_sinks() const {
return sinks_;
}
void MockDisplaySourceConnectionDelegate::GetAvailableSinks(
const SinkInfoListCallback& sinks_callback,
const StringCallback& failure_callback) {
sinks_callback.Run(sinks_);
}
void MockDisplaySourceConnectionDelegate::RequestAuthentication(
int sink_id,
const AuthInfoCallback& auth_info_callback,
const StringCallback& failure_callback) {
DisplaySourceAuthInfo info;
auto it = auth_infos_.find(sink_id);
ASSERT_NE(it, auth_infos_.end());
info.method = it->second.first;
auth_info_callback.Run(info);
}
void MockDisplaySourceConnectionDelegate::Connect(
int sink_id,
const DisplaySourceAuthInfo& auth_info,
const StringCallback& failure_callback) {
auto it = auth_infos_.find(sink_id);
ASSERT_NE(it, auth_infos_.end());
ASSERT_EQ(it->second.first, auth_info.method);
ASSERT_STREQ(it->second.second.c_str(), auth_info.data->c_str());
auto found = std::find_if(sinks_.begin(), sinks_.end(),
[sink_id](const DisplaySourceSinkInfo& sink) {
return sink.id == sink_id;
});
ASSERT_NE(found, sinks_.end());
active_sink_ = sinks_.data() + (found - sinks_.begin());
active_sink_->state = SINK_STATE_CONNECTING;
NotifySinksUpdated();
// Bind sink to udp socket at this stage
// And store udp port to udp_port_ string in order to be used
// In a message exchange. Then make a BrowserThread::PostTask
// on UI thread and call OnSinkConnected() to proceed with the test
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&MockDisplaySourceConnectionDelegate::BindToUdpSocket,
base::Unretained(this)));
}
void MockDisplaySourceConnectionDelegate::Disconnect(
const StringCallback& failure_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CHECK(active_sink_);
ASSERT_EQ(active_sink_->state, SINK_STATE_CONNECTED);
active_sink_->state = SINK_STATE_DISCONNECTED;
active_sink_ = nullptr;
NotifySinksUpdated();
}
void MockDisplaySourceConnectionDelegate::StartWatchingAvailableSinks() {
AddSink(CreateSinkInfo(2, "sink 2"), AUTHENTICATION_METHOD_PBC, "");
}
const DisplaySourceSinkInfo&
MockDisplaySourceConnectionDelegate::GetConnectedSink() const {
CHECK(active_sink_);
return *active_sink_;
}
void MockDisplaySourceConnectionDelegate::StopWatchingAvailableSinks() {}
std::string MockDisplaySourceConnectionDelegate::GetLocalAddress() const {
return "127.0.0.1";
}
std::string MockDisplaySourceConnectionDelegate::GetSinkAddress() const {
return "127.0.0.1";
}
void MockDisplaySourceConnectionDelegate::SendMessage(
const std::string& message) {
ASSERT_FALSE(messages_list_.empty());
ASSERT_FALSE(messages_list_.front().is_from_sink());
CheckSourceMessageContent(messages_list_.front().data, message);
messages_list_.pop_front();
while (!messages_list_.empty() && messages_list_.front().is_from_sink()) {
EnqueueSinkMessage(messages_list_.front().data);
messages_list_.pop_front();
}
}
void MockDisplaySourceConnectionDelegate::SetMessageReceivedCallback(
const StringCallback& callback) {
message_received_cb_ = callback;
}
void MockDisplaySourceConnectionDelegate::AddSink(
DisplaySourceSinkInfo sink,
AuthenticationMethod auth_method,
const std::string& pin_value) {
auth_infos_[sink.id] = {auth_method, pin_value};
sinks_.push_back(std::move(sink));
NotifySinksUpdated();
}
void MockDisplaySourceConnectionDelegate::OnSinkConnected() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CHECK(active_sink_);
active_sink_->state = SINK_STATE_CONNECTED;
NotifySinksUpdated();
}
void MockDisplaySourceConnectionDelegate::NotifySinksUpdated() {
FOR_EACH_OBSERVER(DisplaySourceConnectionDelegate::Observer, observers_,
OnSinksUpdated(sinks_));
}
void MockDisplaySourceConnectionDelegate::
EnqueueSinkMessage(std::string message) {
const std::size_t found_session_key = message.find(kSessionKey);
if (found_session_key != std::string::npos)
AdaptMessagePattern(found_session_key, kSessionKey, kSessionIdLength,
session_id_, message);
const std::size_t found_unicast_key = message.find(kUnicastKey);
if (found_unicast_key != std::string::npos)
AdaptMessagePattern(found_unicast_key, kUnicastKey, kUdpPortLength,
udp_port_, message);
const std::size_t found_clientport_key = message.find(kClientPortKey);
if (found_clientport_key != std::string::npos)
AdaptMessagePattern(found_clientport_key, kClientPortKey, kUdpPortLength,
udp_port_, message);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(message_received_cb_, message));
}
void MockDisplaySourceConnectionDelegate::
CheckSourceMessageContent(std::string pattern,
const std::string& message) {
// Message M6_reply from Source to Sink has a unique and random session id
// generated by Source. The id cannot be predicted and the session id should
// be extracted and added to the message pattern for assertion.
// The following code checks if messages include "Session" string.
// If not, assert the message normally.
// If yes, find the session id, add it to the pattern and to the sink message
// that has Session: substring inside.
const std::size_t found_session_key = message.find(kSessionKey);
if (found_session_key != std::string::npos) {
const std::size_t session_id_pos = found_session_key +
std::char_traits<char>::length(kSessionKey);
session_id_ = message.substr(session_id_pos, kSessionIdLength);
AdaptMessagePattern(found_session_key, kSessionKey, kSessionIdLength,
session_id_, pattern);
}
const std::size_t found_unicast_key = message.find(kUnicastKey);
if (found_unicast_key != std::string::npos)
AdaptMessagePattern(found_unicast_key, kUnicastKey, kUdpPortLength,
udp_port_, pattern);
const std::size_t found_clientport_key = message.find(kClientPortKey);
if (found_clientport_key != std::string::npos)
AdaptMessagePattern(found_clientport_key, kClientPortKey, kUdpPortLength,
udp_port_, pattern);
ASSERT_EQ(pattern, message);
}
void MockDisplaySourceConnectionDelegate::BindToUdpSocket() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
socket_.reset(new net::UDPSocket(
net::DatagramSocket::DEFAULT_BIND, net::RandIntCallback(), nullptr,
net::NetLog::Source()));
net::IPAddress address;
ASSERT_TRUE(address.AssignFromIPLiteral(kLocalHost));
int net_result;
net_result = socket_->Open(net::ADDRESS_FAMILY_IPV4);
ASSERT_EQ(net_result, net::OK);
for (uint16_t port = kPortStart; port < kPortEnd; ++port) {
net::IPEndPoint local_point(address, port);
net_result = socket_->Bind(local_point);
if (net_result == net::OK) {
udp_port_ = std::to_string(port);
// When we got an udp socket established and udp port is known
// Change sink's status to connected and proceed with the test.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&MockDisplaySourceConnectionDelegate::OnSinkConnected,
base::Unretained(this)));
break;
}
}
ReceiveMediaPacket();
}
void MockDisplaySourceConnectionDelegate::ReceiveMediaPacket() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(socket_.get());
const int kBufferSize = 512;
recvfrom_buffer_ = new net::IOBuffer(kBufferSize);
int net_result = socket_->RecvFrom(
recvfrom_buffer_.get(), kBufferSize, &end_point_,
base::Bind(&MockDisplaySourceConnectionDelegate::OnMediaPacketReceived,
base::Unretained(this)));
if (net_result != net::ERR_IO_PENDING)
OnMediaPacketReceived(net_result);
}
void MockDisplaySourceConnectionDelegate::OnMediaPacketReceived(
int net_result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(recvfrom_buffer_.get());
recvfrom_buffer_ = NULL;
if (net_result > 0) {
// We received at least one media packet.
// Test is completed.
socket_->Close();
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&MockDisplaySourceConnectionDelegate::Disconnect,
base::Unretained(this), StringCallback()));
return;
}
DCHECK(socket_.get());
ReceiveMediaPacket();
}
} // namespace extensions