Create QuicSession

This CL depends on the unofficial libquic (https://github.com/devsisters/libquic), with subtle modifications.

BUG=

Review URL: https://codereview.webrtc.org/1648763002

Cr-Commit-Position: refs/heads/master@{#11530}
This commit is contained in:
mikescarlett
2016-02-08 17:35:47 -08:00
committed by Commit bot
parent 456801db3b
commit cd0e4751b2
12 changed files with 1306 additions and 0 deletions

View File

@ -138,6 +138,9 @@
# Enabling this may break interop with Android clients that support H264.
'use_objc_h264%': 0,
# Determines whether QUIC code will be built.
'use_quic%': 0,
'conditions': [
# Enable this to build OpenH264 encoder/FFmpeg decoder. This is supported
# on all platforms except Android and iOS. Because FFmpeg can be built

View File

@ -98,6 +98,22 @@
'FEATURE_ENABLE_PSTN',
],
}],
['use_quic==1', {
'dependencies': [
'<(DEPTH)/third_party/libquic/libquic.gyp:libquic',
],
'sources': [
'quic/quicconnectionhelper.cc',
'quic/quicconnectionhelper.h',
'quic/quicsession.cc',
'quic/quicsession.h',
'quic/reliablequicstream.cc',
'quic/reliablequicstream.h',
],
'export_dependent_settings': [
'<(DEPTH)/third_party/libquic/libquic.gyp:libquic',
],
}],
],
},
{

View File

@ -36,6 +36,15 @@
'client/portallocator_unittest.cc',
'stunprober/stunprober_unittest.cc',
],
'conditions': [
['use_quic==1', {
'sources': [
'quic/quicconnectionhelper_unittest.cc',
'quic/quicsession_unittest.cc',
'quic/reliablequicstream_unittest.cc',
],
}],
],
},
},
],

View File

@ -0,0 +1,74 @@
/*
* Copyright 2016 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 "webrtc/p2p/quic/quicconnectionhelper.h"
namespace cricket {
QuicAlarm* QuicConnectionHelper::CreateAlarm(
net::QuicAlarm::Delegate* delegate) {
return new QuicAlarm(GetClock(), thread_, delegate);
}
QuicAlarm::QuicAlarm(const net::QuicClock* clock,
rtc::Thread* thread,
QuicAlarm::Delegate* delegate)
: net::QuicAlarm(delegate), clock_(clock), thread_(thread) {}
QuicAlarm::~QuicAlarm() {}
void QuicAlarm::OnMessage(rtc::Message* msg) {
// The alarm may have been cancelled.
if (!deadline().IsInitialized()) {
return;
}
// The alarm may have been re-set to a later time.
if (clock_->Now() < deadline()) {
SetImpl();
return;
}
Fire();
}
int64 QuicAlarm::GetDelay() const {
return deadline().Subtract(clock_->Now()).ToMilliseconds();
}
void QuicAlarm::SetImpl() {
DCHECK(deadline().IsInitialized());
CancelImpl(); // Unregister if already posted.
int64 delay_ms = GetDelay();
if (delay_ms < 0) {
delay_ms = 0;
}
thread_->PostDelayed(delay_ms, this);
}
void QuicAlarm::CancelImpl() {
thread_->Clear(this);
}
QuicConnectionHelper::QuicConnectionHelper(rtc::Thread* thread)
: thread_(thread) {}
QuicConnectionHelper::~QuicConnectionHelper() {}
const net::QuicClock* QuicConnectionHelper::GetClock() const {
return &clock_;
}
net::QuicRandom* QuicConnectionHelper::GetRandomGenerator() {
return net::QuicRandom::GetInstance();
}
} // namespace cricket

View File

@ -0,0 +1,66 @@
/*
* Copyright 2016 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 WEBRTC_P2P_QUIC_QUICCONNECTIONHELPER_H_
#define WEBRTC_P2P_QUIC_QUICCONNECTIONHELPER_H_
#include "net/quic/crypto/quic_random.h"
#include "net/quic/quic_alarm.h"
#include "net/quic/quic_clock.h"
#include "net/quic/quic_connection.h"
#include "webrtc/base/thread.h"
namespace cricket {
// An alarm which will go off at a scheduled time, and execute the |OnAlarm|
// method of the delegate.
class QuicAlarm : public net::QuicAlarm, public rtc::MessageHandler {
public:
QuicAlarm(const net::QuicClock* clock,
rtc::Thread* thread,
QuicAlarm::Delegate* delegate);
~QuicAlarm() override;
// rtc::MessageHandler override.
void OnMessage(rtc::Message* msg) override;
// Helper method to get the delay in ms for posting task.
int64 GetDelay() const;
protected:
// net::QuicAlarm overrides.
void SetImpl() override;
void CancelImpl() override;
private:
const net::QuicClock* clock_;
rtc::Thread* thread_;
};
// Helper methods for QuicConnection timing and random number generation.
class QuicConnectionHelper : public net::QuicConnectionHelperInterface {
public:
explicit QuicConnectionHelper(rtc::Thread* thread);
~QuicConnectionHelper() override;
// QuicConnectionHelperInterface overrides.
const net::QuicClock* GetClock() const override;
net::QuicRandom* GetRandomGenerator() override;
QuicAlarm* CreateAlarm(net::QuicAlarm::Delegate* delegate) override;
private:
net::QuicClock clock_;
rtc::Thread* thread_;
};
} // namespace cricket
#endif // WEBRTC_P2P_QUIC_QUICCONNECTIONHELPER_H_

View File

@ -0,0 +1,123 @@
/*
* Copyright 2016 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 "webrtc/p2p/quic/quicconnectionhelper.h"
#include "net/quic/quic_time.h"
#include "webrtc/base/gunit.h"
using cricket::QuicAlarm;
using cricket::QuicConnectionHelper;
using net::QuicClock;
using net::QuicTime;
using net::QuicWallTime;
// Clock that can be set to arbitrary times.
class MockClock : public QuicClock {
public:
MockClock() : now_(QuicTime::Zero()) {}
void AdvanceTime(QuicTime::Delta delta) { now_ = now_.Add(delta); }
QuicTime Now() const override { return now_; }
QuicTime ApproximateNow() const override { return now_; }
QuicWallTime WallNow() const override {
return QuicWallTime::FromUNIXSeconds(
now_.Subtract(QuicTime::Zero()).ToSeconds());
}
base::TimeTicks NowInTicks() const {
base::TimeTicks ticks;
return ticks + base::TimeDelta::FromMicroseconds(
now_.Subtract(QuicTime::Zero()).ToMicroseconds());
}
private:
QuicTime now_;
};
// Implements OnAlarm() event which alarm triggers.
class MockAlarmDelegate : public QuicAlarm::Delegate {
public:
MockAlarmDelegate() : fired_(false) {}
QuicTime OnAlarm() override {
fired_ = true;
return QuicTime::Zero();
}
bool fired() const { return fired_; }
void Clear() { fired_ = false; }
private:
bool fired_;
};
class QuicAlarmTest : public ::testing::Test {
public:
QuicAlarmTest()
: delegate_(new MockAlarmDelegate()),
alarm_(new QuicAlarm(&clock_, rtc::Thread::Current(), delegate_)) {}
// Make the alarm fire after the given microseconds (us). Negative values
// imply the alarm should fire immediately.
void SetTime(int us) {
QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(us);
alarm_->Set(clock_.Now().Add(delta));
}
// Make rtc::Thread::Current() process the next message.
void ProcessNextMessage() { rtc::Thread::Current()->ProcessMessages(0); }
protected:
// Handles event that alarm fires.
MockAlarmDelegate* delegate_;
// Used for setting clock time relative to alarm.
MockClock clock_;
scoped_ptr<QuicAlarm> alarm_;
};
// Test that the alarm is fired.
TEST_F(QuicAlarmTest, FireAlarm) {
SetTime(-1);
ProcessNextMessage();
ASSERT_TRUE(delegate_->fired());
ASSERT_EQ(QuicTime::Zero(), alarm_->deadline());
}
// Test cancellation of alarm when it is set to fire.
TEST_F(QuicAlarmTest, CancelAlarmAfterSet) {
// TODO(mikescarlett): Test will fail in the future if
// rtc::Thread::PostDelayed calls the delegate synchronously for times <= 0.
// Rewrite this when rtc::Thread is able to use a mock clock.
SetTime(-1);
alarm_->Cancel();
ProcessNextMessage();
ASSERT_FALSE(delegate_->fired());
}
// Test cancellation of alarm when it is not set to fire.
TEST_F(QuicAlarmTest, CancelAlarmBeforeSet) {
alarm_->Cancel();
ProcessNextMessage();
ASSERT_FALSE(delegate_->fired());
}
// Test that timing for posting task is accurate.
TEST_F(QuicAlarmTest, AlarmGetDelay) {
SetTime(1000000);
EXPECT_EQ(1000, alarm_->GetDelay());
clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(300000));
EXPECT_EQ(700, alarm_->GetDelay());
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2016 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 "webrtc/p2p/quic/quicsession.h"
#include <string>
#include <utility>
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/messagehandler.h"
#include "webrtc/base/messagequeue.h"
namespace cricket {
QuicSession::QuicSession(scoped_ptr<net::QuicConnection> connection,
const net::QuicConfig& config)
: net::QuicSession(connection.release(), config) {}
QuicSession::~QuicSession() {}
void QuicSession::StartClientHandshake(
net::QuicCryptoClientStream* crypto_stream) {
SetCryptoStream(crypto_stream);
net::QuicSession::Initialize();
crypto_stream->CryptoConnect();
}
void QuicSession::StartServerHandshake(
net::QuicCryptoServerStream* crypto_stream) {
SetCryptoStream(crypto_stream);
net::QuicSession::Initialize();
}
void QuicSession::SetCryptoStream(net::QuicCryptoStream* crypto_stream) {
crypto_stream_.reset(crypto_stream);
}
bool QuicSession::ExportKeyingMaterial(base::StringPiece label,
base::StringPiece context,
size_t result_len,
string* result) {
return crypto_stream_->ExportKeyingMaterial(label, context, result_len,
result);
}
void QuicSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
net::QuicSession::OnCryptoHandshakeEvent(event);
if (event == HANDSHAKE_CONFIRMED) {
LOG(INFO) << "QuicSession handshake complete";
RTC_DCHECK(IsEncryptionEstablished());
RTC_DCHECK(IsCryptoHandshakeConfirmed());
SignalHandshakeComplete();
}
}
ReliableQuicStream* QuicSession::CreateIncomingDynamicStream(
net::QuicStreamId id) {
ReliableQuicStream* stream = CreateDataStream(id);
if (stream) {
SignalIncomingStream(stream);
}
return stream;
}
ReliableQuicStream* QuicSession::CreateOutgoingDynamicStream(
net::SpdyPriority priority) {
ReliableQuicStream* stream = CreateDataStream(GetNextOutgoingStreamId());
if (stream) {
ActivateStream(stream);
}
return stream;
}
ReliableQuicStream* QuicSession::CreateDataStream(net::QuicStreamId id) {
if (crypto_stream_ == nullptr || !crypto_stream_->encryption_established()) {
// Encryption not active so no stream created
return nullptr;
}
return new ReliableQuicStream(id, this);
}
void QuicSession::OnConnectionClosed(net::QuicErrorCode error, bool from_peer) {
net::QuicSession::OnConnectionClosed(error, from_peer);
SignalConnectionClosed(error, from_peer);
}
bool QuicSession::OnReadPacket(const char* data, size_t data_len) {
net::QuicEncryptedPacket packet(data, data_len);
connection()->ProcessUdpPacket(connection()->self_address(),
connection()->peer_address(), packet);
return true;
}
} // namespace cricket

View File

@ -0,0 +1,94 @@
/*
* Copyright 2016 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 WEBRTC_P2P_QUIC_QUICSESSION_H_
#define WEBRTC_P2P_QUIC_QUICSESSION_H_
#include <string>
#include "net/quic/quic_crypto_client_stream.h"
#include "net/quic/quic_crypto_server_stream.h"
#include "net/quic/quic_crypto_stream.h"
#include "net/quic/quic_session.h"
#include "webrtc/base/constructormagic.h"
#include "webrtc/base/sigslot.h"
#include "webrtc/base/sslidentity.h"
#include "webrtc/p2p/quic/reliablequicstream.h"
namespace cricket {
// This class provides a QUIC session over peer-to-peer transport that
// negotiates the crypto handshake (using QuicCryptoHandshake) and provides
// reading/writing of data using QUIC packets.
class QuicSession : public net::QuicSession, public sigslot::has_slots<> {
public:
QuicSession(scoped_ptr<net::QuicConnection> connection,
const net::QuicConfig& config);
~QuicSession() override;
// Initiates client crypto handshake by sending client hello.
void StartClientHandshake(net::QuicCryptoClientStream* crypto_stream);
// Responds to a client who has inititated the crypto handshake.
void StartServerHandshake(net::QuicCryptoServerStream* crypto_stream);
// QuicSession overrides.
net::QuicCryptoStream* GetCryptoStream() override {
return crypto_stream_.get();
}
// TODO(mikescarlett): Verify whether outgoing streams should be owned by
// caller. It appears these are deleted in the net::QuicSession destructor,
// but Chromium's documentation says this should not happen.
ReliableQuicStream* CreateOutgoingDynamicStream(
net::SpdyPriority priority) override;
// QuicSession optional overrides.
void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
// QuicConnectionVisitorInterface overrides.
void OnConnectionClosed(net::QuicErrorCode error, bool from_peer) override;
// Exports keying material for SRTP.
bool ExportKeyingMaterial(base::StringPiece label,
base::StringPiece context,
size_t result_len,
string* result);
// Decrypts an incoming QUIC packet to a data stream.
bool OnReadPacket(const char* data, size_t data_len);
// Called when peers have established forward-secure encryption
sigslot::signal0<> SignalHandshakeComplete;
// Called when connection closes locally, or remotely by peer.
sigslot::signal2<net::QuicErrorCode, bool> SignalConnectionClosed;
// Called when an incoming QUIC stream is created so we can process data
// from it by registering a listener to
// ReliableQuicStream::SignalDataReceived.
sigslot::signal1<ReliableQuicStream*> SignalIncomingStream;
protected:
// Sets the QUIC crypto stream and takes ownership of it.
void SetCryptoStream(net::QuicCryptoStream* crypto_stream);
// QuicSession override.
ReliableQuicStream* CreateIncomingDynamicStream(
net::QuicStreamId id) override;
virtual ReliableQuicStream* CreateDataStream(net::QuicStreamId id);
private:
scoped_ptr<net::QuicCryptoStream> crypto_stream_;
RTC_DISALLOW_COPY_AND_ASSIGN(QuicSession);
};
} // namespace cricket
#endif // WEBRTC_P2P_QUIC_QUICSESSION_H_

View File

@ -0,0 +1,462 @@
/*
* Copyright 2016 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 "webrtc/p2p/quic/quicsession.h"
#include <string>
#include <vector>
#include "net/base/ip_endpoint.h"
#include "net/quic/crypto/crypto_server_config_protobuf.h"
#include "net/quic/crypto/quic_random.h"
#include "net/quic/crypto/proof_source.h"
#include "net/quic/crypto/proof_verifier.h"
#include "net/quic/crypto/quic_crypto_client_config.h"
#include "net/quic/crypto/quic_crypto_server_config.h"
#include "net/quic/quic_crypto_client_stream.h"
#include "net/quic/quic_crypto_server_stream.h"
#include "webrtc/base/common.h"
#include "webrtc/base/gunit.h"
#include "webrtc/p2p/base/faketransportcontroller.h"
#include "webrtc/p2p/quic/quicconnectionhelper.h"
#include "webrtc/p2p/quic/reliablequicstream.h"
using net::IPAddressNumber;
using net::IPEndPoint;
using net::Perspective;
using net::ProofVerifyContext;
using net::ProofVerifyDetails;
using net::QuicByteCount;
using net::QuicClock;
using net::QuicConfig;
using net::QuicConnection;
using net::QuicCryptoClientConfig;
using net::QuicCryptoServerConfig;
using net::QuicCryptoClientStream;
using net::QuicCryptoServerStream;
using net::QuicCryptoStream;
using net::QuicErrorCode;
using net::QuicPacketWriter;
using net::QuicRandom;
using net::QuicServerConfigProtobuf;
using net::QuicServerId;
using net::QuicStreamId;
using net::WriteResult;
using net::WriteStatus;
using cricket::FakeTransportChannel;
using cricket::QuicConnectionHelper;
using cricket::QuicSession;
using cricket::ReliableQuicStream;
using cricket::TransportChannel;
using rtc::Thread;
// Timeout for running asynchronous operations within unit tests.
const int kTimeoutMs = 1000;
// Testing SpdyPriority value for creating outgoing ReliableQuicStream.
const uint8 kDefaultPriority = 3;
// TExport keying material function
const char kExporterLabel[] = "label";
const char kExporterContext[] = "context";
const size_t kExporterContextLen = sizeof(kExporterContext);
// Identifies QUIC server session
const QuicServerId kServerId("www.google.com", 443);
// Used by QuicCryptoServerConfig to provide server credentials, returning a
// canned response equal to |success|.
class FakeProofSource : public net::ProofSource {
public:
explicit FakeProofSource(bool success) : success_(success) {}
// ProofSource override.
bool GetProof(const net::IPAddressNumber& server_ip,
const std::string& hostname,
const std::string& server_config,
bool ecdsa_ok,
const std::vector<std::string>** out_certs,
std::string* out_signature,
std::string* out_leaf_cert_sct) override {
if (success_) {
std::vector<std::string>* certs = new std::vector<std::string>();
certs->push_back("Required to establish handshake");
std::string signature("Signature");
*out_certs = certs;
*out_signature = signature;
}
return success_;
}
private:
// Whether or not obtaining proof source succeeds.
bool success_;
};
// Used by QuicCryptoClientConfig to verify server credentials, returning a
// canned response of QUIC_SUCCESS if |success| is true.
class FakeProofVerifier : public net::ProofVerifier {
public:
explicit FakeProofVerifier(bool success) : success_(success) {}
// ProofVerifier override
net::QuicAsyncStatus VerifyProof(
const std::string& hostname,
const std::string& server_config,
const std::vector<std::string>& certs,
const std::string& cert_sct,
const std::string& signature,
const net::ProofVerifyContext* verify_context,
std::string* error_details,
scoped_ptr<net::ProofVerifyDetails>* verify_details,
net::ProofVerifierCallback* callback) override {
return success_ ? net::QUIC_SUCCESS : net::QUIC_FAILURE;
}
private:
// Whether or not proof verification succeeds.
bool success_;
};
// Writes QUIC packets to a fake transport channel that simulates a network.
class FakeQuicPacketWriter : public QuicPacketWriter {
public:
explicit FakeQuicPacketWriter(FakeTransportChannel* fake_channel)
: fake_channel_(fake_channel) {}
// Sends packets across the network.
WriteResult WritePacket(const char* buffer,
size_t buf_len,
const IPAddressNumber& self_address,
const IPEndPoint& peer_address) override {
rtc::PacketOptions packet_options;
int rv = fake_channel_->SendPacket(buffer, buf_len, packet_options, 0);
net::WriteStatus status;
if (rv > 0) {
status = net::WRITE_STATUS_OK;
} else if (fake_channel_->GetError() == EWOULDBLOCK) {
status = net::WRITE_STATUS_BLOCKED;
} else {
status = net::WRITE_STATUS_ERROR;
}
return net::WriteResult(status, rv);
}
// Returns true if the writer buffers and subsequently rewrites data
// when an attempt to write results in the underlying socket becoming
// write blocked.
bool IsWriteBlockedDataBuffered() const override { return true; }
// Returns true if the network socket is not writable.
bool IsWriteBlocked() const override { return !fake_channel_->writable(); }
// Records that the socket has become writable, for example when an EPOLLOUT
// is received or an asynchronous write completes.
void SetWritable() override { fake_channel_->SetWritable(true); }
// Returns the maximum size of the packet which can be written using this
// writer for the supplied peer address. This size may actually exceed the
// size of a valid QUIC packet.
QuicByteCount GetMaxPacketSize(
const IPEndPoint& peer_address) const override {
return net::kMaxPacketSize;
}
private:
FakeTransportChannel* fake_channel_;
};
// Creates a FakePacketWriter for a given QuicConnection instance.
class FakePacketWriterFactory : public QuicConnection::PacketWriterFactory {
public:
explicit FakePacketWriterFactory(FakeTransportChannel* channel)
: channel_(channel) {}
QuicPacketWriter* Create(QuicConnection* connection) const override {
return new FakeQuicPacketWriter(channel_);
}
private:
FakeTransportChannel* channel_;
};
// Wrapper for QuicSession and transport channel that stores incoming data.
class QuicSessionForTest : public QuicSession {
public:
QuicSessionForTest(scoped_ptr<net::QuicConnection> connection,
const net::QuicConfig& config,
scoped_ptr<FakeTransportChannel> channel)
: QuicSession(std::move(connection), config),
channel_(std::move(channel)) {
channel_->SignalReadPacket.connect(
this, &QuicSessionForTest::OnChannelReadPacket);
}
// Called when channel has packets to read.
void OnChannelReadPacket(TransportChannel* channel,
const char* data,
size_t size,
const rtc::PacketTime& packet_time,
int flags) {
OnReadPacket(data, size);
}
// Called when peer receives incoming stream from another peer.
void OnIncomingStream(ReliableQuicStream* stream) {
stream->SignalDataReceived.connect(this,
&QuicSessionForTest::OnDataReceived);
last_incoming_stream_ = stream;
}
// Called when peer has data to read from incoming stream.
void OnDataReceived(net::QuicStreamId id, const char* data, size_t length) {
last_received_data_ = std::string(data, length);
}
std::string data() { return last_received_data_; }
bool has_data() { return data().size() > 0; }
FakeTransportChannel* channel() { return channel_.get(); }
ReliableQuicStream* incoming_stream() { return last_incoming_stream_; }
private:
// Transports QUIC packets to/from peer.
scoped_ptr<FakeTransportChannel> channel_;
// Stores data received by peer once it is sent from the other peer.
std::string last_received_data_;
// Handles incoming streams from sender.
ReliableQuicStream* last_incoming_stream_ = nullptr;
};
// Simulates data transfer between two peers using QUIC.
class QuicSessionTest : public ::testing::Test,
public QuicCryptoClientStream::ProofHandler {
public:
QuicSessionTest() : quic_helper_(rtc::Thread::Current()) {}
// Instantiates |client_peer_| and |server_peer_|.
void CreateClientAndServerSessions();
scoped_ptr<QuicSessionForTest> CreateSession(
scoped_ptr<FakeTransportChannel> channel,
Perspective perspective);
QuicCryptoClientStream* CreateCryptoClientStream(QuicSessionForTest* session,
bool handshake_success);
QuicCryptoServerStream* CreateCryptoServerStream(QuicSessionForTest* session,
bool handshake_success);
scoped_ptr<QuicConnection> CreateConnection(FakeTransportChannel* channel,
Perspective perspective);
void StartHandshake(bool client_handshake_success,
bool server_handshake_success);
// Test handshake establishment and sending/receiving of data.
void TestStreamConnection(QuicSessionForTest* from_session,
QuicSessionForTest* to_session);
// Test that client and server are not connected after handshake failure.
void TestDisconnectAfterFailedHandshake();
// QuicCryptoClientStream::ProofHelper overrides.
void OnProofValid(
const QuicCryptoClientConfig::CachedState& cached) override {}
void OnProofVerifyDetailsAvailable(
const ProofVerifyDetails& verify_details) override {}
protected:
QuicConnectionHelper quic_helper_;
QuicConfig config_;
QuicClock clock_;
scoped_ptr<QuicSessionForTest> client_peer_;
scoped_ptr<QuicSessionForTest> server_peer_;
};
// Initializes "client peer" who begins crypto handshake and "server peer" who
// establishes encryption with client.
void QuicSessionTest::CreateClientAndServerSessions() {
scoped_ptr<FakeTransportChannel> channel1(
new FakeTransportChannel(nullptr, "channel1", 0));
scoped_ptr<FakeTransportChannel> channel2(
new FakeTransportChannel(nullptr, "channel2", 0));
// Prevent channel1->OnReadPacket and channel2->OnReadPacket from calling
// themselves in a loop, which causes to future packets to be recursively
// consumed while the current thread blocks consumption of current ones.
channel2->SetAsync(true);
// Configure peers to send packets to each other.
channel1->Connect();
channel2->Connect();
channel1->SetDestination(channel2.get());
client_peer_ = CreateSession(std::move(channel1), Perspective::IS_CLIENT);
server_peer_ = CreateSession(std::move(channel2), Perspective::IS_SERVER);
}
scoped_ptr<QuicSessionForTest> QuicSessionTest::CreateSession(
scoped_ptr<FakeTransportChannel> channel,
Perspective perspective) {
scoped_ptr<QuicConnection> quic_connection =
CreateConnection(channel.get(), perspective);
return scoped_ptr<QuicSessionForTest>(new QuicSessionForTest(
std::move(quic_connection), config_, std::move(channel)));
}
QuicCryptoClientStream* QuicSessionTest::CreateCryptoClientStream(
QuicSessionForTest* session,
bool handshake_success) {
QuicCryptoClientConfig* client_config =
new QuicCryptoClientConfig(new FakeProofVerifier(handshake_success));
return new QuicCryptoClientStream(
kServerId, session, new ProofVerifyContext(), client_config, this);
}
QuicCryptoServerStream* QuicSessionTest::CreateCryptoServerStream(
QuicSessionForTest* session,
bool handshake_success) {
QuicCryptoServerConfig* server_config =
new QuicCryptoServerConfig("TESTING", QuicRandom::GetInstance(),
new FakeProofSource(handshake_success));
// Provide server with serialized config string to prove ownership.
QuicCryptoServerConfig::ConfigOptions options;
QuicServerConfigProtobuf* primary_config = server_config->GenerateConfig(
QuicRandom::GetInstance(), &clock_, options);
server_config->AddConfig(primary_config, clock_.WallNow());
return new QuicCryptoServerStream(server_config, session);
}
scoped_ptr<QuicConnection> QuicSessionTest::CreateConnection(
FakeTransportChannel* channel,
Perspective perspective) {
FakePacketWriterFactory writer_factory(channel);
IPAddressNumber ip(net::kIPv4AddressSize, 0);
bool owns_writer = true;
return scoped_ptr<QuicConnection>(new QuicConnection(
0, net::IPEndPoint(ip, 0), &quic_helper_, writer_factory, owns_writer,
perspective, net::QuicSupportedVersions()));
}
void QuicSessionTest::StartHandshake(bool client_handshake_success,
bool server_handshake_success) {
server_peer_->StartServerHandshake(
CreateCryptoServerStream(server_peer_.get(), server_handshake_success));
client_peer_->StartClientHandshake(
CreateCryptoClientStream(client_peer_.get(), client_handshake_success));
}
void QuicSessionTest::TestStreamConnection(QuicSessionForTest* from_session,
QuicSessionForTest* to_session) {
// Wait for crypto handshake to finish then check if encryption established.
ASSERT_TRUE_WAIT(from_session->IsCryptoHandshakeConfirmed() &&
to_session->IsCryptoHandshakeConfirmed(),
kTimeoutMs);
ASSERT_TRUE(from_session->IsEncryptionEstablished());
ASSERT_TRUE(to_session->IsEncryptionEstablished());
string from_key;
string to_key;
bool from_success = from_session->ExportKeyingMaterial(
kExporterLabel, kExporterContext, kExporterContextLen, &from_key);
ASSERT_TRUE(from_success);
bool to_success = to_session->ExportKeyingMaterial(
kExporterLabel, kExporterContext, kExporterContextLen, &to_key);
ASSERT_TRUE(to_success);
EXPECT_EQ(from_key.size(), kExporterContextLen);
EXPECT_EQ(from_key, to_key);
// Now we can establish encrypted outgoing stream.
ReliableQuicStream* outgoing_stream =
from_session->CreateOutgoingDynamicStream(kDefaultPriority);
ASSERT_NE(nullptr, outgoing_stream);
EXPECT_TRUE(from_session->HasOpenDynamicStreams());
outgoing_stream->SignalDataReceived.connect(
from_session, &QuicSessionForTest::OnDataReceived);
to_session->SignalIncomingStream.connect(
to_session, &QuicSessionForTest::OnIncomingStream);
// Send a test message from peer 1 to peer 2.
const char kTestMessage[] = "Hello, World!";
outgoing_stream->Write(kTestMessage, strlen(kTestMessage));
// Wait for peer 2 to receive messages.
ASSERT_TRUE_WAIT(to_session->has_data(), kTimeoutMs);
ReliableQuicStream* incoming = to_session->incoming_stream();
ASSERT_TRUE(incoming);
EXPECT_TRUE(to_session->HasOpenDynamicStreams());
EXPECT_EQ(to_session->data(), kTestMessage);
// Send a test message from peer 2 to peer 1.
const char kTestResponse[] = "Response";
incoming->Write(kTestResponse, strlen(kTestResponse));
// Wait for peer 1 to receive messages.
ASSERT_TRUE_WAIT(from_session->has_data(), kTimeoutMs);
EXPECT_EQ(from_session->data(), kTestResponse);
}
// Client and server should disconnect when proof verification fails.
void QuicSessionTest::TestDisconnectAfterFailedHandshake() {
EXPECT_TRUE_WAIT(!client_peer_->connection()->connected(), kTimeoutMs);
EXPECT_TRUE_WAIT(!server_peer_->connection()->connected(), kTimeoutMs);
EXPECT_FALSE(client_peer_->IsEncryptionEstablished());
EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed());
EXPECT_FALSE(server_peer_->IsEncryptionEstablished());
EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed());
}
// Establish encryption then send message from client to server.
TEST_F(QuicSessionTest, ClientToServer) {
CreateClientAndServerSessions();
StartHandshake(true, true);
TestStreamConnection(client_peer_.get(), server_peer_.get());
}
// Establish encryption then send message from server to client.
TEST_F(QuicSessionTest, ServerToClient) {
CreateClientAndServerSessions();
StartHandshake(true, true);
TestStreamConnection(server_peer_.get(), client_peer_.get());
}
// Make client fail to verify proof from server.
TEST_F(QuicSessionTest, ClientRejection) {
CreateClientAndServerSessions();
StartHandshake(false, true);
TestDisconnectAfterFailedHandshake();
}
// Make server fail to give proof to client.
TEST_F(QuicSessionTest, ServerRejection) {
CreateClientAndServerSessions();
StartHandshake(true, false);
TestDisconnectAfterFailedHandshake();
}
// Test that data streams are not created before handshake.
TEST_F(QuicSessionTest, CannotCreateDataStreamBeforeHandshake) {
CreateClientAndServerSessions();
EXPECT_EQ(nullptr, server_peer_->CreateOutgoingDynamicStream(5));
EXPECT_EQ(nullptr, client_peer_->CreateOutgoingDynamicStream(5));
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2016 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 "webrtc/p2p/quic/reliablequicstream.h"
#include <string>
#include "webrtc/base/checks.h"
namespace cricket {
ReliableQuicStream::ReliableQuicStream(net::QuicStreamId id,
net::QuicSession* session)
: net::ReliableQuicStream(id, session) {
RTC_DCHECK_NE(net::kCryptoStreamId, id);
}
ReliableQuicStream::~ReliableQuicStream() {}
void ReliableQuicStream::OnDataAvailable() {
struct iovec iov;
while (sequencer()->GetReadableRegions(&iov, 1) == 1) {
SignalDataReceived(id(), reinterpret_cast<const char*>(iov.iov_base),
iov.iov_len);
sequencer()->MarkConsumed(iov.iov_len);
}
}
void ReliableQuicStream::OnClose() {
net::ReliableQuicStream::OnClose();
SignalClosed(id(), connection_error());
}
rtc::StreamResult ReliableQuicStream::Write(const char* data, size_t len) {
// Writes the data, or buffers it.
WriteOrBufferData(std::string(data, len), false, nullptr);
if (HasBufferedData()) {
return rtc::StreamResult(rtc::SR_BLOCK);
}
return rtc::StreamResult(rtc::SR_SUCCESS);
}
} // namespace cricket

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016 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 WEBRTC_P2P_QUIC_RELIABLEQUICSTREAM_H_
#define WEBRTC_P2P_QUIC_RELIABLEQUICSTREAM_H_
#include "net/quic/reliable_quic_stream.h"
#include "webrtc/base/constructormagic.h"
#include "webrtc/base/sigslot.h"
#include "webrtc/base/stream.h"
namespace cricket {
// Streams created by QuicSession.
class ReliableQuicStream : public net::ReliableQuicStream,
public sigslot::has_slots<> {
public:
ReliableQuicStream(net::QuicStreamId id, net::QuicSession* session);
~ReliableQuicStream() override;
// ReliableQuicStream overrides.
void OnDataAvailable() override;
void OnClose() override;
net::SpdyPriority Priority() const override { return 0; }
// Process decrypted data into encrypted QUIC packets, which get sent to the
// QuicPacketWriter. rtc::SR_BLOCK is returned if the operation blocks instead
// of writing, in which case the data is queued until OnCanWrite() is called.
rtc::StreamResult Write(const char* data, size_t len);
// Called when decrypted data is ready to be read.
sigslot::signal3<net::QuicStreamId, const char*, size_t> SignalDataReceived;
// Called when stream closed.
sigslot::signal2<net::QuicStreamId, net::QuicErrorCode> SignalClosed;
private:
RTC_DISALLOW_COPY_AND_ASSIGN(ReliableQuicStream);
};
} // namespace cricket
#endif // WEBRTC_P2P_QUIC_RELIABLEQUICSTREAM_H_

View File

@ -0,0 +1,255 @@
/*
* Copyright 2016 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 "webrtc/p2p/quic/reliablequicstream.h"
#include <string>
#include "net/base/ip_address_number.h"
#include "net/quic/quic_connection.h"
#include "net/quic/quic_protocol.h"
#include "net/quic/quic_session.h"
#include "webrtc/base/buffer.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/sigslot.h"
#include "webrtc/base/stream.h"
#include "webrtc/p2p/quic/quicconnectionhelper.h"
using cricket::QuicConnectionHelper;
using cricket::ReliableQuicStream;
using net::FecProtection;
using net::IPAddressNumber;
using net::IPEndPoint;
using net::Perspective;
using net::QuicAckListenerInterface;
using net::QuicConfig;
using net::QuicConnection;
using net::QuicConsumedData;
using net::QuicCryptoStream;
using net::QuicErrorCode;
using net::QuicIOVector;
using net::QuicPacketWriter;
using net::QuicRstStreamErrorCode;
using net::QuicSession;
using net::QuicStreamId;
using net::QuicStreamOffset;
using net::SpdyPriority;
using rtc::SR_SUCCESS;
using rtc::SR_BLOCK;
// QuicSession that does not create streams and writes data from
// ReliableQuicStream to a string.
class MockQuicSession : public QuicSession {
public:
MockQuicSession(QuicConnection* connection,
const QuicConfig& config,
std::string* write_buffer)
: QuicSession(connection, config), write_buffer_(write_buffer) {}
// Writes outgoing data from ReliableQuicStream to a string.
QuicConsumedData WritevData(
QuicStreamId id,
QuicIOVector iovector,
QuicStreamOffset offset,
bool fin,
FecProtection fec_protection,
QuicAckListenerInterface* ack_notifier_delegate) override {
if (!writable_) {
return QuicConsumedData(0, false);
}
const char* data = reinterpret_cast<const char*>(iovector.iov->iov_base);
size_t len = iovector.total_length;
write_buffer_->append(data, len);
return QuicConsumedData(len, false);
}
net::ReliableQuicStream* CreateIncomingDynamicStream(
QuicStreamId id) override {
return nullptr;
}
net::ReliableQuicStream* CreateOutgoingDynamicStream(
SpdyPriority priority) override {
return nullptr;
}
QuicCryptoStream* GetCryptoStream() override { return nullptr; }
// Called by ReliableQuicStream when they want to close stream.
void SendRstStream(QuicStreamId id,
QuicRstStreamErrorCode error,
QuicStreamOffset bytes_written) override {}
// Sets whether data is written to buffer, or else if this is write blocked.
void set_writable(bool writable) { writable_ = writable; }
// Tracks whether the stream is write blocked and its priority.
void register_write_blocked_stream(QuicStreamId stream_id,
SpdyPriority priority) {
write_blocked_streams()->RegisterStream(stream_id, priority);
}
private:
// Stores written data from ReliableQuicStream.
std::string* write_buffer_;
// Whether data is written to write_buffer_.
bool writable_ = true;
};
// Packet writer that does nothing. This is required for QuicConnection but
// isn't used for writing data.
class DummyPacketWriter : public QuicPacketWriter {
public:
DummyPacketWriter() {}
// QuicPacketWriter overrides.
virtual net::WriteResult WritePacket(const char* buffer,
size_t buf_len,
const IPAddressNumber& self_address,
const IPEndPoint& peer_address) {
return net::WriteResult(net::WRITE_STATUS_ERROR, 0);
}
bool IsWriteBlockedDataBuffered() const override { return false; }
bool IsWriteBlocked() const override { return false; };
void SetWritable() override {}
net::QuicByteCount GetMaxPacketSize(
const net::IPEndPoint& peer_address) const override {
return 0;
}
};
// QuicPacketWriter is not necessary, so this creates a packet writer that
// doesn't do anything.
class DummyPacketWriterFactory : public QuicConnection::PacketWriterFactory {
public:
DummyPacketWriterFactory() {}
QuicPacketWriter* Create(QuicConnection* connection) const override {
return new DummyPacketWriter();
}
};
class ReliableQuicStreamTest : public ::testing::Test,
public sigslot::has_slots<> {
public:
ReliableQuicStreamTest() {}
void CreateReliableQuicStream() {
const net::QuicStreamId kStreamId = 5;
// Arbitrary values for QuicConnection.
QuicConnectionHelper* quic_helper =
new QuicConnectionHelper(rtc::Thread::Current());
Perspective perspective = Perspective::IS_SERVER;
net::IPAddressNumber ip(net::kIPv4AddressSize, 0);
bool owns_writer = false;
QuicConnection* connection = new QuicConnection(
0, IPEndPoint(ip, 0), quic_helper, DummyPacketWriterFactory(),
owns_writer, perspective, net::QuicSupportedVersions());
session_.reset(
new MockQuicSession(connection, QuicConfig(), &write_buffer_));
stream_.reset(new ReliableQuicStream(kStreamId, session_.get()));
stream_->SignalDataReceived.connect(
this, &ReliableQuicStreamTest::OnDataReceived);
stream_->SignalClosed.connect(this, &ReliableQuicStreamTest::OnClosed);
session_->register_write_blocked_stream(stream_->id(), stream_->Priority());
}
void OnDataReceived(QuicStreamId id, const char* data, size_t length) {
ASSERT_EQ(id, stream_->id());
read_buffer_.append(data, length);
}
void OnClosed(QuicStreamId id, QuicErrorCode err) { closed_ = true; }
protected:
scoped_ptr<ReliableQuicStream> stream_;
scoped_ptr<MockQuicSession> session_;
// Data written by the ReliableQuicStream.
std::string write_buffer_;
// Data read by the ReliableQuicStream.
std::string read_buffer_;
// Whether the ReliableQuicStream is closed.
bool closed_ = false;
};
// Write an entire string.
TEST_F(ReliableQuicStreamTest, WriteDataWhole) {
CreateReliableQuicStream();
EXPECT_EQ(SR_SUCCESS, stream_->Write("Foo bar", 7));
EXPECT_EQ("Foo bar", write_buffer_);
}
// Write part of a string.
TEST_F(ReliableQuicStreamTest, WriteDataPartial) {
CreateReliableQuicStream();
EXPECT_EQ(SR_SUCCESS, stream_->Write("Hello, World!", 8));
EXPECT_EQ("Hello, W", write_buffer_);
}
// Test that strings are buffered correctly.
TEST_F(ReliableQuicStreamTest, BufferData) {
CreateReliableQuicStream();
session_->set_writable(false);
EXPECT_EQ(SR_BLOCK, stream_->Write("Foo bar", 7));
EXPECT_EQ(0ul, write_buffer_.size());
EXPECT_TRUE(stream_->HasBufferedData());
session_->set_writable(true);
stream_->OnCanWrite();
EXPECT_FALSE(stream_->HasBufferedData());
EXPECT_EQ("Foo bar", write_buffer_);
EXPECT_EQ(SR_SUCCESS, stream_->Write("xyzzy", 5));
EXPECT_EQ("Foo barxyzzy", write_buffer_);
}
// Read an entire string.
TEST_F(ReliableQuicStreamTest, ReadDataWhole) {
CreateReliableQuicStream();
net::QuicStreamFrame frame(-1, false, 0, "Hello, World!");
stream_->OnStreamFrame(frame);
EXPECT_EQ("Hello, World!", read_buffer_);
}
// Read part of a string.
TEST_F(ReliableQuicStreamTest, ReadDataPartial) {
CreateReliableQuicStream();
net::QuicStreamFrame frame(-1, false, 0, "Hello, World!");
frame.frame_length = 5;
stream_->OnStreamFrame(frame);
EXPECT_EQ("Hello", read_buffer_);
}
// Test that closing the stream results in a callback.
TEST_F(ReliableQuicStreamTest, CloseStream) {
CreateReliableQuicStream();
EXPECT_FALSE(closed_);
stream_->OnClose();
EXPECT_TRUE(closed_);
}