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:
@ -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
|
||||
|
||||
@ -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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
74
webrtc/p2p/quic/quicconnectionhelper.cc
Normal file
74
webrtc/p2p/quic/quicconnectionhelper.cc
Normal 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
|
||||
66
webrtc/p2p/quic/quicconnectionhelper.h
Normal file
66
webrtc/p2p/quic/quicconnectionhelper.h
Normal 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_
|
||||
123
webrtc/p2p/quic/quicconnectionhelper_unittest.cc
Normal file
123
webrtc/p2p/quic/quicconnectionhelper_unittest.cc
Normal 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());
|
||||
}
|
||||
103
webrtc/p2p/quic/quicsession.cc
Normal file
103
webrtc/p2p/quic/quicsession.cc
Normal 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
|
||||
94
webrtc/p2p/quic/quicsession.h
Normal file
94
webrtc/p2p/quic/quicsession.h
Normal 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_
|
||||
462
webrtc/p2p/quic/quicsession_unittest.cc
Normal file
462
webrtc/p2p/quic/quicsession_unittest.cc
Normal 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));
|
||||
}
|
||||
51
webrtc/p2p/quic/reliablequicstream.cc
Normal file
51
webrtc/p2p/quic/reliablequicstream.cc
Normal 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
|
||||
50
webrtc/p2p/quic/reliablequicstream.h
Normal file
50
webrtc/p2p/quic/reliablequicstream.h
Normal 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_
|
||||
255
webrtc/p2p/quic/reliablequicstream_unittest.cc
Normal file
255
webrtc/p2p/quic/reliablequicstream_unittest.cc
Normal 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_);
|
||||
}
|
||||
Reference in New Issue
Block a user