Commit 1377ae3e authored by Victor Vasiliev's avatar Victor Vasiliev Committed by Commit Bot

Synchronize QuaRTC code up to the Wed Jul 25 20:25:29 EDT 2018

This reverts commit 8cc4e50c and relands
812abd15 with iOS tests disabled.

R=rch@chromium.org

Change-Id: I56fb4be93602fcaa9deccdf9a1837ebcd787af11
Reviewed-on: https://chromium-review.googlesource.com/1154361Reviewed-by: default avatarZhongyi Shi <zhongyi@chromium.org>
Commit-Queue: Victor Vasiliev <vasilvv@chromium.org>
Cr-Commit-Position: refs/heads/master@{#579229}
parent 6d3e637f
......@@ -1561,20 +1561,14 @@ component("net") {
"third_party/quic/platform/impl/quic_url_impl.h",
"third_party/quic/platform/impl/quic_url_utils_impl.cc",
"third_party/quic/platform/impl/quic_url_utils_impl.h",
"third_party/quic/quartc/quartc_clock_interface.h",
"third_party/quic/quartc/quartc_factory.cc",
"third_party/quic/quartc/quartc_factory.h",
"third_party/quic/quartc/quartc_factory_interface.cc",
"third_party/quic/quartc/quartc_factory_interface.h",
"third_party/quic/quartc/quartc_packet_writer.cc",
"third_party/quic/quartc/quartc_packet_writer.h",
"third_party/quic/quartc/quartc_session.cc",
"third_party/quic/quartc/quartc_session.h",
"third_party/quic/quartc/quartc_session_interface.h",
"third_party/quic/quartc/quartc_stream.cc",
"third_party/quic/quartc/quartc_stream.h",
"third_party/quic/quartc/quartc_stream_interface.h",
"third_party/quic/quartc/quartc_task_runner_interface.h",
"third_party/spdy/core/fuzzing/hpack_fuzz_util.cc",
"third_party/spdy/core/fuzzing/hpack_fuzz_util.h",
"third_party/spdy/core/hpack/hpack_constants.cc",
......
// Copyright (c) 2017 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.
#ifndef NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_CLOCK_INTERFACE_H_
#define NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_CLOCK_INTERFACE_H_
#include <stdint.h>
namespace quic {
// Implemented by the Quartc API user to provide a timebase.
class QuartcClockInterface {
public:
virtual ~QuartcClockInterface() {}
virtual int64_t NowMicroseconds() = 0;
};
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_CLOCK_INTERFACE_H_
......@@ -8,34 +8,83 @@
#include "net/third_party/quic/core/quic_alarm_factory.h"
#include "net/third_party/quic/core/quic_connection.h"
#include "net/third_party/quic/core/quic_simple_buffer_allocator.h"
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/quartc/quartc_factory_interface.h"
#include "net/third_party/quic/quartc/quartc_packet_writer.h"
#include "net/third_party/quic/quartc/quartc_task_runner_interface.h"
#include "net/third_party/quic/quartc/quartc_session.h"
namespace quic {
// Implements the QuartcFactoryInterface to create the instances of
// QuartcSessionInterface. Implements the QuicAlarmFactory to create alarms
// using the QuartcTaskRunner. Implements the QuicConnectionHelperInterface used
// by the QuicConnections. Only one QuartcFactory is expected to be created.
class QUIC_EXPORT_PRIVATE QuartcFactory : public QuartcFactoryInterface,
public QuicAlarmFactory,
public QuicConnectionHelperInterface {
// Options that control the BBR algorithm.
enum class QuartcBbrOptions {
kSlowerStartup, // Once a loss is encountered in STARTUP,
// switches startup to a 1.5x pacing gain.
kFullyDrainQueue, // Fully drains the queue once per cycle.
kReduceProbeRtt, // Probe RTT reduces CWND to 0.75 * BDP instead of 4
// packets.
kSkipProbeRtt, // Skip Probe RTT and extend the existing min_rtt if a
// recent min_rtt is within 12.5% of the current min_rtt.
kSkipProbeRttAggressively, // Skip ProbeRTT and extend the existing min_rtt
// as long as you've been app limited at least
// once.
kFillUpLinkDuringProbing, // Sends probing retransmissions whenever we
// become application limited.
kInitialWindow3, // Use a 3-packet initial congestion window.
kInitialWindow10, // Use a 10-packet initial congestion window.
kInitialWindow20, // Use a 20-packet initial congestion window.
kInitialWindow50, // Use a 50-packet initial congestion window.
kStartup1RTT, // Stay in STARTUP for 1 RTT.
kStartup2RTT, // Stay in STARTUP for 2 RTTs.
};
// The configuration for creating a QuartcFactory.
struct QuartcFactoryConfig {
// Factory for |QuicAlarm|s. Implemented by the Quartc user with different
// mechanisms. For example in WebRTC, it is implemented with rtc::Thread.
// Owned by the user, and needs to stay alive for as long as the QuartcFactory
// exists.
QuicAlarmFactory* alarm_factory = nullptr;
// The clock used by |QuicAlarm|s. Implemented by the Quartc user. Owned by
// the user, and needs to stay alive for as long as the QuartcFactory exists.
QuicClock* clock = nullptr;
};
struct QuartcSessionConfig {
// When using Quartc, there are two endpoints. The QuartcSession on one
// endpoint must act as a server and the one on the other side must act as a
// client.
Perspective perspective = Perspective::IS_CLIENT;
// This is only needed when is_server = false. It must be unique
// for each endpoint the local endpoint may communicate with. For example,
// a WebRTC client could use the remote endpoint's crypto fingerprint
QuicString unique_remote_server_id;
// The way the QuicConnection will send and receive packets, like a virtual
// UDP socket. For WebRTC, this will typically be an IceTransport.
QuartcPacketTransport* packet_transport = nullptr;
// The maximum size of the packet can be written with the packet writer.
// 1200 bytes by default.
QuicPacketLength max_packet_size = 1200;
// Options to control the BBR algorithm. In case the congestion control is
// set to anything but BBR, these options are ignored.
std::vector<QuartcBbrOptions> bbr_options;
// Timeouts for the crypto handshake. Set them to higher values to
// prevent closing the session before it started on a slow network.
// Zero entries are ignored and QUIC defaults are used in that case.
QuicTime::Delta max_idle_time_before_crypto_handshake =
QuicTime::Delta::Zero();
QuicTime::Delta max_time_before_crypto_handshake = QuicTime::Delta::Zero();
QuicTime::Delta idle_network_timeout = QuicTime::Delta::Zero();
};
// Factory that creates instances of QuartcSession. Implements the
// QuicConnectionHelperInterface used by the QuicConnections. Only one
// QuartcFactory is expected to be created.
class QUIC_EXPORT_PRIVATE QuartcFactory : public QuicConnectionHelperInterface {
public:
explicit QuartcFactory(const QuartcFactoryConfig& factory_config);
~QuartcFactory() override;
// QuartcFactoryInterface overrides.
std::unique_ptr<QuartcSessionInterface> CreateQuartcSession(
const QuartcSessionConfig& quartc_session_config) override;
// QuicAlarmFactory overrides.
QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) override;
QuicArenaScopedPtr<QuicAlarm> CreateAlarm(
QuicArenaScopedPtr<QuicAlarm::Delegate> delegate,
QuicConnectionArena* arena) override;
// Creates a new QuartcSession using the given configuration.
std::unique_ptr<QuartcSession> CreateQuartcSession(
const QuartcSessionConfig& quartc_session_config);
// QuicConnectionHelperInterface overrides.
const QuicClock* GetClock() const override;
......@@ -49,15 +98,19 @@ class QUIC_EXPORT_PRIVATE QuartcFactory : public QuartcFactoryInterface,
Perspective perspective,
QuartcPacketWriter* packet_writer);
// Used to implement QuicAlarmFactory..
QuartcTaskRunnerInterface* task_runner_;
// Used to implement the QuicConnectionHelperInterface.
// The QuicClock wrapper held in this variable is owned by QuartcFactory,
// but the QuartcClockInterface inside of it belongs to the user!
std::unique_ptr<QuicClock> clock_;
// Used to implement QuicAlarmFactory. Owned by the user and must outlive
// QuartcFactory.
QuicAlarmFactory* alarm_factory_;
// Used to implement the QuicConnectionHelperInterface. Owned by the user and
// must outlive QuartcFactory.
QuicClock* clock_;
SimpleBufferAllocator buffer_allocator_;
};
// Creates a new instance of QuartcFactory.
std::unique_ptr<QuartcFactory> CreateQuartcFactory(
const QuartcFactoryConfig& factory_config);
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_FACTORY_H_
// Copyright (c) 2017 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 "net/third_party/quic/quartc/quartc_factory_interface.h"
namespace quic {
QuartcFactoryInterface::QuartcSessionConfig::QuartcSessionConfig() = default;
QuartcFactoryInterface::QuartcSessionConfig::~QuartcSessionConfig() = default;
} // namespace quic
// Copyright (c) 2017 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.
#ifndef NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_FACTORY_INTERFACE_H_
#define NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_FACTORY_INTERFACE_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/quartc/quartc_clock_interface.h"
#include "net/third_party/quic/quartc/quartc_session_interface.h"
#include "net/third_party/quic/quartc/quartc_task_runner_interface.h"
namespace quic {
// Algorithm to use for congestion control.
enum class QuartcCongestionControl {
kDefault, // Use an arbitrary algorithm chosen by QUIC.
kBBR, // Use BBR.
};
// Options that control the BBR algorithm.
enum class QuartcBbrOptions {
kSlowerStartup, // Once a loss is encountered in STARTUP,
// switches startup to a 1.5x pacing gain.
kFullyDrainQueue, // Fully drains the queue once per cycle.
kReduceProbeRtt, // Probe RTT reduces CWND to 0.75 * BDP instead of 4
// packets.
kSkipProbeRtt, // Skip Probe RTT and extend the existing min_rtt if a
// recent min_rtt is within 12.5% of the current min_rtt.
kSkipProbeRttAggressively, // Skip ProbeRTT and extend the existing min_rtt
// as long as you've been app limited at least
// once.
kFillUpLinkDuringProbing, // Sends probing retransmissions whenever we
// become application limited.
kInitialWindow3, // Use a 3-packet initial congestion window.
kInitialWindow10, // Use a 10-packet initial congestion window.
kInitialWindow20, // Use a 20-packet initial congestion window.
kInitialWindow50, // Use a 50-packet initial congestion window.
kStartup1RTT, // Stay in STARTUP for 1 RTT.
kStartup2RTT, // Stay in STARTUP for 2 RTTs.
};
// Used to create instances for Quartc objects such as QuartcSession.
class QUIC_EXPORT_PRIVATE QuartcFactoryInterface {
public:
virtual ~QuartcFactoryInterface() {}
struct QuartcSessionConfig {
QuartcSessionConfig();
~QuartcSessionConfig();
// When using Quartc, there are two endpoints. The QuartcSession on one
// endpoint must act as a server and the one on the other side must act as a
// client.
bool is_server = false;
// This is only needed when is_server = false. It must be unique
// for each endpoint the local endpoint may communicate with. For example,
// a WebRTC client could use the remote endpoint's crypto fingerprint
std::string unique_remote_server_id;
// The way the QuicConnection will send and receive packets, like a virtual
// UDP socket. For WebRTC, this will typically be an IceTransport.
QuartcPacketTransport* packet_transport = nullptr;
// The maximum size of the packet can be written with the packet writer.
// 1200 bytes by default.
uint64_t max_packet_size = 1200;
// Algorithm to use for congestion control. By default, uses an arbitrary
// congestion control algorithm chosen by QUIC.
QuartcCongestionControl congestion_control =
QuartcCongestionControl::kDefault;
// Options to control the BBR algorithm. In case the congestion control is
// set to anything but BBR, these options are ignored.
std::vector<QuartcBbrOptions> bbr_options;
// Timeouts for the crypto handshake. Set them to higher values to
// prevent closing the session before it started on a slow network.
// Zero entries are ignored and QUIC defaults are used in that case.
uint32_t max_idle_time_before_crypto_handshake_secs = 0;
uint32_t max_time_before_crypto_handshake_secs = 0;
QuicTime::Delta idle_network_timeout = QuicTime::Delta::Zero();
};
virtual std::unique_ptr<QuartcSessionInterface> CreateQuartcSession(
const QuartcSessionConfig& quartc_config) = 0;
};
// The configuration for creating a QuartcFactory.
struct QuartcFactoryConfig {
// The task runner used by the QuartcAlarm. Implemented by the Quartc user
// with different mechanism. For example in WebRTC, it is implemented with
// rtc::Thread. Owned by the user, and needs to stay alive for as long
// as the QuartcFactory exists.
QuartcTaskRunnerInterface* task_runner = nullptr;
// The clock used by QuartcAlarms. Implemented by the Quartc user. Owned by
// the user, and needs to stay alive for as long as the QuartcFactory exists.
QuartcClockInterface* clock = nullptr;
};
// Creates a new instance of QuartcFactoryInterface.
std::unique_ptr<QuartcFactoryInterface> CreateQuartcFactory(
const QuartcFactoryConfig& factory_config);
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_FACTORY_INTERFACE_H_
......@@ -5,12 +5,31 @@
#ifndef NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_PACKET_WRITER_H_
#define NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_PACKET_WRITER_H_
#include "net/third_party/quic/core/quic_connection.h"
#include "net/third_party/quic/core/quic_packet_writer.h"
#include "net/third_party/quic/core/quic_types.h"
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/quartc/quartc_session_interface.h"
namespace quic {
// Send and receive packets, like a virtual UDP socket. For example, this
// could be implemented by WebRTC's IceTransport.
class QUIC_EXPORT_PRIVATE QuartcPacketTransport {
public:
// Additional metadata provided for each packet written.
struct PacketInfo {
QuicPacketNumber packet_number;
};
virtual ~QuartcPacketTransport() {}
// Called by the QuartcPacketWriter when writing packets to the network.
// Return the number of written bytes. Return 0 if the write is blocked.
virtual int Write(const char* buffer,
size_t buf_len,
const PacketInfo& info) = 0;
};
// Implements a QuicPacketWriter using a QuartcPacketTransport, which allows a
// QuicConnection to use (for example), a WebRTC IceTransport.
class QUIC_EXPORT_PRIVATE QuartcPacketWriter : public QuicPacketWriter {
......@@ -20,7 +39,7 @@ class QUIC_EXPORT_PRIVATE QuartcPacketWriter : public QuicPacketWriter {
~QuartcPacketWriter() override {}
// The QuicConnection calls WritePacket and the QuicPacketWriter writes them
// to the QuartcSessionInterface::PacketTransport.
// to the QuartcSession::PacketTransport.
WriteResult WritePacket(const char* buffer,
size_t buf_len,
const QuicIpAddress& self_address,
......
......@@ -11,9 +11,7 @@
#include "net/third_party/quic/core/quic_error_codes.h"
#include "net/third_party/quic/core/quic_session.h"
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/quartc/quartc_clock_interface.h"
#include "net/third_party/quic/quartc/quartc_packet_writer.h"
#include "net/third_party/quic/quartc/quartc_session_interface.h"
#include "net/third_party/quic/quartc/quartc_stream.h"
namespace quic {
......@@ -28,58 +26,23 @@ class QuartcCryptoServerStreamHelper : public QuicCryptoServerStream::Helper {
const QuicSocketAddress& client_address,
const QuicSocketAddress& peer_address,
const QuicSocketAddress& self_address,
std::string* error_details) const override;
};
// Adapts |QuartcSessionVisitor|s to the |QuicConnectionDebugVisitor| interface.
// Keeps a set of |QuartcSessionVisitor|s and forwards QUIC debug callbacks to
// each visitor in the set.
class QuartcSessionVisitorAdapter : public QuicConnectionDebugVisitor {
public:
QuartcSessionVisitorAdapter();
~QuartcSessionVisitorAdapter() override;
void OnPacketSent(const SerializedPacket& serialized_packet,
QuicPacketNumber original_packet_number,
TransmissionType transmission_type,
QuicTime sent_time) override;
void OnIncomingAck(const QuicAckFrame& ack_frame,
QuicTime ack_receive_time,
QuicPacketNumber largest_observed,
bool rtt_updated,
QuicPacketNumber least_unacked_sent_packet) override;
void OnPacketLoss(QuicPacketNumber lost_packet_number,
TransmissionType transmission_type,
QuicTime detection_time) override;
void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame,
const QuicTime& receive_time) override;
void OnSuccessfulVersionNegotiation(
const ParsedQuicVersion& version) override;
const std::set<QuartcSessionVisitor*>& visitors() const { return visitors_; }
std::set<QuartcSessionVisitor*>& mutable_visitors() { return visitors_; }
// Disallow copy and assign.
QuartcSessionVisitorAdapter(const QuartcSessionVisitorAdapter&) = delete;
QuartcSessionVisitorAdapter operator=(const QuartcSessionVisitorAdapter&) =
delete;
private:
std::set<QuartcSessionVisitor*> visitors_;
QuicString* error_details) const override;
};
// QuartcSession owns and manages a QUIC connection.
class QUIC_EXPORT_PRIVATE QuartcSession
: public QuicSession,
public QuartcSessionInterface,
public QuicCryptoClientStream::ProofHandler {
public:
QuartcSession(std::unique_ptr<QuicConnection> connection,
const QuicConfig& config,
const std::string& unique_remote_server_id,
const QuicString& unique_remote_server_id,
Perspective perspective,
QuicConnectionHelperInterface* helper,
QuicClock* clock,
std::unique_ptr<QuartcPacketWriter> packet_writer);
QuartcSession(const QuartcSession&) = delete;
QuartcSession& operator=(const QuartcSession&) = delete;
~QuartcSession() override;
// QuicSession overrides.
......@@ -91,48 +54,67 @@ class QUIC_EXPORT_PRIVATE QuartcSession
void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override;
void CloseStream(QuicStreamId stream_id) override;
// QuicConnectionVisitorInterface overrides.
void OnConnectionClosed(QuicErrorCode error,
const std::string& error_details,
const QuicString& error_details,
ConnectionCloseSource source) override;
// QuartcSessionInterface overrides
void SetPreSharedKey(QuicStringPiece key) override;
// QuartcSession methods.
void StartCryptoHandshake() override;
// Sets a pre-shared key for use during the crypto handshake. Must be set
// before StartCryptoHandshake() is called.
void SetPreSharedKey(QuicStringPiece key);
bool ExportKeyingMaterial(const std::string& label,
const uint8_t* context,
size_t context_len,
bool used_context,
uint8_t* result,
size_t result_len) override;
void StartCryptoHandshake();
void CloseConnection(const std::string& details) override;
// Closes the connection with the given human-readable error details.
// The connection closes with the QUIC_CONNECTION_CANCELLED error code to
// indicate the application closed it.
//
// Informs the peer that the connection has been closed. This prevents the
// peer from waiting until the connection times out.
//
// Cleans up the underlying QuicConnection's state. Closing the connection
// makes it safe to delete the QuartcSession.
void CloseConnection(const QuicString& details);
QuartcStreamInterface* CreateOutgoingStream(
const OutgoingStreamParameters& param) override;
// If the given stream is still open, sends a reset frame to cancel it.
// Note: This method cancels a stream by QuicStreamId rather than by pointer
// (or by a method on QuartcStream) because QuartcSession (and not
// the caller) owns the streams. Streams may finish and be deleted before the
// caller tries to cancel them, rendering the caller's pointers invalid.
void CancelStream(QuicStreamId stream_id);
void CancelStream(QuicStreamId stream_id) override;
// Callbacks called by the QuartcSession to notify the user of the
// QuartcSession of certain events.
class Delegate {
public:
virtual ~Delegate() {}
bool IsOpenStream(QuicStreamId stream_id) override;
// Called when the crypto handshake is complete.
virtual void OnCryptoHandshakeComplete() = 0;
QuicConnectionStats GetStats() override;
// Called when a new stream is received from the remote endpoint.
virtual void OnIncomingStream(QuartcStream* stream) = 0;
void SetDelegate(QuartcSessionInterface::Delegate* session_delegate) override;
// Called when the connection is closed. This means all of the streams will
// be closed and no new streams can be created.
virtual void OnConnectionClosed(QuicErrorCode error_code,
const QuicString& error_details,
ConnectionCloseSource source) = 0;
void AddSessionVisitor(QuartcSessionVisitor* visitor) override;
void RemoveSessionVisitor(QuartcSessionVisitor* visitor) override;
// TODO(zhihuang): Add proof verification.
};
void OnTransportCanWrite() override;
// The |delegate| is not owned by QuartcSession.
void SetDelegate(Delegate* session_delegate);
// Decrypts an incoming QUIC packet to a data stream.
bool OnTransportReceived(const char* data, size_t data_len) override;
// Called when CanWrite() changes from false to true.
void OnTransportCanWrite();
void BundleWrites() override;
void FlushWrites() override;
// Called when a packet has been received and should be handled by the
// QuicConnection.
bool OnTransportReceived(const char* data, size_t data_len);
// ProofHandler overrides.
void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override;
......@@ -143,12 +125,6 @@ class QUIC_EXPORT_PRIVATE QuartcSession
void OnProofVerifyDetailsAvailable(
const ProofVerifyDetails& verify_details) override;
// Override the default crypto configuration.
// The session will take the ownership of the configurations.
void SetClientCryptoConfig(QuicCryptoClientConfig* client_config);
void SetServerCryptoConfig(QuicCryptoServerConfig* server_config);
protected:
// QuicSession override.
QuicStream* CreateIncomingDynamicStream(QuicStreamId id) override;
......@@ -164,7 +140,7 @@ class QUIC_EXPORT_PRIVATE QuartcSession
private:
// For crypto handshake.
std::unique_ptr<QuicCryptoStream> crypto_stream_;
const std::string unique_remote_server_id_;
const QuicString unique_remote_server_id_;
Perspective perspective_;
// Packet writer used by |connection_|.
......@@ -179,7 +155,7 @@ class QUIC_EXPORT_PRIVATE QuartcSession
// For recording packet receipt time
QuicClock* clock_;
// Not owned by QuartcSession.
QuartcSessionInterface::Delegate* session_delegate_ = nullptr;
Delegate* session_delegate_ = nullptr;
// Used by QUIC crypto server stream to track most recently compressed certs.
std::unique_ptr<QuicCompressedCertsCache> quic_compressed_certs_cache_;
// This helper is needed when create QuicCryptoServerStream.
......@@ -188,14 +164,6 @@ class QUIC_EXPORT_PRIVATE QuartcSession
std::unique_ptr<QuicCryptoClientConfig> quic_crypto_client_config_;
// Config for QUIC crypto server stream, used by the server.
std::unique_ptr<QuicCryptoServerConfig> quic_crypto_server_config_;
// Holds pointers to QuartcSessionVisitors and adapts them to the
// QuicConnectionDebugVisitor interface.
QuartcSessionVisitorAdapter session_visitor_adapter_;
std::unique_ptr<QuicConnection::ScopedPacketFlusher> packet_flusher_;
DISALLOW_COPY_AND_ASSIGN(QuartcSession);
};
} // namespace quic
......
// Copyright (c) 2017 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.
#ifndef NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_SESSION_INTERFACE_H_
#define NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_SESSION_INTERFACE_H_
#include <stddef.h>
#include <stdint.h>
#include <string>
#include "net/third_party/quic/core/quic_bandwidth.h"
#include "net/third_party/quic/core/quic_error_codes.h"
#include "net/third_party/quic/core/quic_time.h"
#include "net/third_party/quic/core/quic_types.h"
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/quartc/quartc_session_visitor_interface.h"
#include "net/third_party/quic/quartc/quartc_stream_interface.h"
namespace quic {
// Send and receive packets, like a virtual UDP socket. For example, this
// could be implemented by WebRTC's IceTransport.
class QUIC_EXPORT_PRIVATE QuartcPacketTransport {
public:
// Additional metadata provided for each packet written.
struct PacketInfo {
QuicPacketNumber packet_number;
};
virtual ~QuartcPacketTransport() {}
// Called by the QuartcPacketWriter when writing packets to the network.
// Return the number of written bytes. Return 0 if the write is blocked.
virtual int Write(const char* buffer,
size_t buf_len,
const PacketInfo& info) = 0;
};
// Given a PacketTransport, provides a way to send and receive separate streams
// of reliable, in-order, encrypted data. For example, this can build on top of
// a WebRTC IceTransport for sending and receiving data over QUIC.
class QUIC_EXPORT_PRIVATE QuartcSessionInterface {
public:
virtual ~QuartcSessionInterface() {}
// Sets a pre-shared key for use during the crypto handshake. Must be set
// before StartCryptoHandshake() is called.
virtual void SetPreSharedKey(QuicStringPiece key) = 0;
virtual void StartCryptoHandshake() = 0;
// Only needed when using SRTP with QuicTransport
// Key Exporter interface from RFC 5705
// Arguments are:
// label -- the exporter label.
// part of the RFC defining each exporter usage (IN)
// context/context_len -- a context to bind to for this connection;
// optional, can be NULL, 0 (IN)
// use_context -- whether to use the context value
// (needed to distinguish no context from
// zero-length ones).
// result -- where to put the computed value
// result_len -- the length of the computed value
virtual bool ExportKeyingMaterial(const std::string& label,
const uint8_t* context,
size_t context_len,
bool used_context,
uint8_t* result,
size_t result_len) = 0;
// Closes the connection with the given human-readable error details.
// The connection closes with the QUIC_CONNECTION_CANCELLED error code to
// indicate the application closed it.
//
// Informs the peer that the connection has been closed. This prevents the
// peer from waiting until the connection times out.
//
// Cleans up the underlying QuicConnection's state. Closing the connection
// makes it safe to delete the QuartcSession.
virtual void CloseConnection(const std::string& error_details) = 0;
// For forward-compatibility. More parameters could be added through the
// struct without changing the API.
struct OutgoingStreamParameters {};
virtual QuartcStreamInterface* CreateOutgoingStream(
const OutgoingStreamParameters& params) = 0;
// If the given stream is still open, sends a reset frame to cancel it.
// Note: This method cancels a stream by QuicStreamId rather than by pointer
// (or by a method on QuartcStreamInterface) because QuartcSession (and not
// the caller) owns the streams. Streams may finish and be deleted before the
// caller tries to cancel them, rendering the caller's pointers invalid.
virtual void CancelStream(QuicStreamId stream_id) = 0;
// This method verifies if a stream is still open and stream pointer can be
// used. When true is returned, the interface pointer is good for making a
// call immediately on the same thread, but may be rendered invalid by ANY
// other QUIC activity.
virtual bool IsOpenStream(QuicStreamId stream_id) = 0;
// Gets stats associated with the current QUIC connection.
virtual QuicConnectionStats GetStats() = 0;
// Called when CanWrite() changes from false to true.
virtual void OnTransportCanWrite() = 0;
// Called when a packet has been received and should be handled by the
// QuicConnection.
virtual bool OnTransportReceived(const char* data, size_t data_len) = 0;
// Bundles subsequent writes on a best-effort basis.
// Data is sent whenever enough data is accumulated to fill a packet.
// The session stops bundling writes and sends data immediately as soon as
// FlushWrites() is called or a packet is received.
virtual void BundleWrites() = 0;
// Stop bundling writes and flush any pending writes immediately.
virtual void FlushWrites() = 0;
// Callbacks called by the QuartcSession to notify the user of the
// QuartcSession of certain events.
class Delegate {
public:
virtual ~Delegate() {}
// Called when the crypto handshake is complete.
virtual void OnCryptoHandshakeComplete() = 0;
// Called when a new stream is received from the remote endpoint.
virtual void OnIncomingStream(QuartcStreamInterface* stream) = 0;
// Called when the connection is closed. This means all of the streams will
// be closed and no new streams can be created.
// TODO(zhihuang): Create mapping from integer error code to WebRTC error
// code.
virtual void OnConnectionClosed(int error_code, bool from_remote) = 0;
// TODO(zhihuang): Add proof verification.
};
// The |delegate| is not owned by QuartcSession.
virtual void SetDelegate(Delegate* delegate) = 0;
// Add or remove session visitors. Session visitors observe internals of the
// Quartc/QUIC session for the purpose of gathering metrics or debug
// information.
virtual void AddSessionVisitor(QuartcSessionVisitor* visitor) = 0;
virtual void RemoveSessionVisitor(QuartcSessionVisitor* visitor) = 0;
};
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_SESSION_INTERFACE_H_
// Copyright (c) 2018 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.
#ifndef NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_SESSION_VISITOR_INTERFACE_H_
#define NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_SESSION_VISITOR_INTERFACE_H_
#include "net/third_party/quic/core/quic_connection.h"
#include "net/third_party/quic/platform/api/quic_export.h"
namespace quic {
// QuartcSessionVisitor observes internals of a Quartc/QUIC session for the
// purpose of gathering metrics or debug information.
class QUIC_EXPORT_PRIVATE QuartcSessionVisitor {
public:
virtual ~QuartcSessionVisitor() {}
// Informs this visitor of a |QuicConnection| for the session.
// Called once when the visitor is attached to a QuartcSession, or when a new
// |QuicConnection| starts.
virtual void OnQuicConnection(QuicConnection* connection) {}
// Called when a packet has been sent.
virtual void OnPacketSent(const SerializedPacket& serialized_packet,
QuicPacketNumber original_packet_number,
TransmissionType transmission_type,
QuicTime sent_time) {}
// Called when an ack is received.
virtual void OnIncomingAck(const QuicAckFrame& ack_frame,
QuicTime ack_receive_time,
QuicPacketNumber largest_observed,
bool rtt_updated,
QuicPacketNumber least_unacked_sent_packet) {}
// Called when a packet is lost.
virtual void OnPacketLoss(QuicPacketNumber lost_packet_number,
TransmissionType transmission_type,
QuicTime detection_time) {}
// Called when a WindowUpdateFrame is received.
virtual void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame,
const QuicTime& receive_time) {}
// Called when version negotiation succeeds.
virtual void OnSuccessfulVersionNegotiation(
const ParsedQuicVersion& version) {}
};
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_SESSION_VISITOR_INTERFACE_H_
......@@ -50,39 +50,11 @@ void QuartcStream::OnDataBuffered(
delegate_->OnBufferChanged(this);
}
uint32_t QuartcStream::stream_id() {
return id();
}
uint64_t QuartcStream::bytes_buffered() {
return BufferedDataBytes();
}
bool QuartcStream::fin_sent() {
return QuicStream::fin_sent();
}
int QuartcStream::stream_error() {
return QuicStream::stream_error();
}
void QuartcStream::Write(QuicMemSliceSpan data, const WriteParameters& param) {
WriteMemSlices(data, param.fin);
}
void QuartcStream::FinishWriting() {
WriteOrBufferData(QuicStringPiece(nullptr, 0), true, nullptr);
}
void QuartcStream::FinishReading() {
QuicStream::StopReading();
}
void QuartcStream::Close() {
QuicStream::session()->CloseStream(id());
}
void QuartcStream::SetDelegate(QuartcStreamInterface::Delegate* delegate) {
void QuartcStream::SetDelegate(Delegate* delegate) {
if (delegate_) {
LOG(WARNING) << "The delegate for Stream " << id()
<< " has already been set.";
......
......@@ -8,13 +8,15 @@
#include "net/third_party/quic/core/quic_session.h"
#include "net/third_party/quic/core/quic_stream.h"
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/quartc/quartc_stream_interface.h"
#include "net/third_party/quic/platform/api/quic_mem_slice_span.h"
namespace quic {
// Implements a QuartcStreamInterface using a QuicStream.
class QUIC_EXPORT_PRIVATE QuartcStream : public QuicStream,
public QuartcStreamInterface {
// Sends and receives data with a particular QUIC stream ID, reliably and
// in-order. To send/receive data out of order, use separate streams. To
// send/receive unreliably, close a stream after reliability is no longer
// needed.
class QUIC_EXPORT_PRIVATE QuartcStream : public QuicStream {
public:
QuartcStream(QuicStreamId id, QuicSession* session);
......@@ -33,27 +35,43 @@ class QUIC_EXPORT_PRIVATE QuartcStream : public QuicStream,
const QuicReferenceCountedPointer<QuicAckListenerInterface>& ack_listener)
override;
// QuartcStreamInterface overrides.
uint32_t stream_id() override;
uint64_t bytes_buffered() override;
bool fin_sent() override;
int stream_error() override;
void Write(QuicMemSliceSpan data, const WriteParameters& param) override;
void FinishWriting() override;
void FinishReading() override;
void Close() override;
void SetDelegate(QuartcStreamInterface::Delegate* delegate) override;
// QuartcStream interface methods.
// Marks this stream as finished writing. Asynchronously sends a FIN and
// closes the write-side. It is not necessary to call FinishWriting() if the
// last call to Write() sends a FIN.
void FinishWriting();
// Implemented by the user of the QuartcStream to receive incoming
// data and be notified of state changes.
class Delegate {
public:
virtual ~Delegate() {}
// Called when the stream receives data. Called with |size| == 0 after all
// stream data has been delivered (once the stream receives a FIN bit).
// Note that the same packet may include both data and a FIN bit, causing
// this method to be called twice.
virtual void OnReceived(QuartcStream* stream,
const char* data,
size_t size) = 0;
// Called when the stream is closed, either locally or by the remote
// endpoint. Streams close when (a) fin bits are both sent and received,
// (b) Close() is called, or (c) the stream is reset.
// TODO(zhihuang) Creates a map from the integer error_code to WebRTC native
// error code.
virtual void OnClose(QuartcStream* stream) = 0;
// Called when the contents of the stream's buffer changes.
virtual void OnBufferChanged(QuartcStream* stream) = 0;
};
// The |delegate| is not owned by QuartcStream.
void SetDelegate(Delegate* delegate);
private:
QuartcStreamInterface::Delegate* delegate_ = nullptr;
Delegate* delegate_ = nullptr;
};
} // namespace quic
......
// Copyright (c) 2017 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.
#ifndef NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_STREAM_INTERFACE_H_
#define NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_STREAM_INTERFACE_H_
#include <stddef.h>
#include <stdint.h>
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/platform/api/quic_mem_slice_span.h"
namespace quic {
// Sends and receives data with a particular QUIC stream ID, reliably and
// in-order. To send/receive data out of order, use separate streams. To
// send/receive unreliably, close a stream after reliability is no longer
// needed.
class QUIC_EXPORT_PRIVATE QuartcStreamInterface {
public:
virtual ~QuartcStreamInterface() {}
// The QUIC stream ID.
virtual uint32_t stream_id() = 0;
// The amount of data buffered on this stream.
virtual uint64_t bytes_buffered() = 0;
// Return true if the FIN has been sent. Used by the outgoing streams to
// determine if all the data has been sent
virtual bool fin_sent() = 0;
virtual int stream_error() = 0;
struct WriteParameters {
// |fin| is set to be true when there is no more data need to be send
// through a particular stream. The receiving side will used it to determine
// if the sender finish sending data.
bool fin = false;
};
// Sends data reliably and in-order. Returns the amount sent.
// Does not buffer data.
virtual void Write(QuicMemSliceSpan data, const WriteParameters& param) = 0;
// Marks this stream as finished writing. Asynchronously sends a FIN and
// closes the write-side. The stream will no longer call OnCanWrite().
// It is not necessary to call FinishWriting() if the last call to Write()
// sends a FIN.
virtual void FinishWriting() = 0;
// Marks this stream as finished reading. Further incoming data is discarded.
// The stream will no longer call OnReceived().
// It is never necessary to call FinishReading(). The read-side closes when a
// FIN is received, regardless of whether FinishReading() has been called.
virtual void FinishReading() = 0;
// Once Close is called, no more data can be sent, all buffered data will be
// dropped and no data will be retransmitted.
virtual void Close() = 0;
// Implemented by the user of the QuartcStreamInterface to receive incoming
// data and be notified of state changes.
class Delegate {
public:
virtual ~Delegate() {}
// Called when the stream receives the data. Called with |size| == 0 after
// all stream data has been delivered.
virtual void OnReceived(QuartcStreamInterface* stream,
const char* data,
size_t size) = 0;
// Called when the stream is closed, either locally or by the remote
// endpoint. Streams close when (a) fin bits are both sent and received,
// (b) Close() is called, or (c) the stream is reset.
// TODO(zhihuang) Creates a map from the integer error_code to WebRTC native
// error code.
virtual void OnClose(QuartcStreamInterface* stream) = 0;
// Called when the contents of the stream's buffer changes.
virtual void OnBufferChanged(QuartcStreamInterface* stream) = 0;
};
// The |delegate| is not owned by QuartcStream.
virtual void SetDelegate(Delegate* delegate) = 0;
};
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_STREAM_INTERFACE_H_
......@@ -11,9 +11,9 @@
#include "net/third_party/quic/platform/api/quic_ptr_util.h"
#include "net/third_party/quic/platform/api/quic_test.h"
#include "net/third_party/quic/platform/api/quic_test_mem_slice_vector.h"
#include "net/third_party/quic/quartc/quartc_clock_interface.h"
#include "net/third_party/quic/quartc/quartc_factory.h"
#include "net/third_party/quic/test_tools/mock_clock.h"
#include "net/third_party/quic/test_tools/quic_test_utils.h"
#include "net/third_party/spdy/core/spdy_protocol.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -23,7 +23,6 @@ namespace quic {
namespace {
static const QuicStreamId kStreamId = 5;
static const QuartcStreamInterface::WriteParameters kDefaultParam;
// MockQuicSession that does not create streams and writes data from
// QuicStream to a string.
......@@ -31,7 +30,7 @@ class MockQuicSession : public QuicSession {
public:
MockQuicSession(QuicConnection* connection,
const QuicConfig& config,
std::string* write_buffer)
QuicString* write_buffer)
: QuicSession(connection, nullptr /*visitor*/, config),
write_buffer_(write_buffer) {}
......@@ -92,7 +91,7 @@ class MockQuicSession : public QuicSession {
private:
// Stores written data from ReliableQuicStreamAdapter.
std::string* write_buffer_;
QuicString* write_buffer_;
// Whether data is written to write_buffer_.
bool writable_ = true;
};
......@@ -132,23 +131,23 @@ class DummyPacketWriter : public QuicPacketWriter {
WriteResult Flush() override { return WriteResult(WRITE_STATUS_OK, 0); }
};
class MockQuartcStreamDelegate : public QuartcStreamInterface::Delegate {
class MockQuartcStreamDelegate : public QuartcStream::Delegate {
public:
MockQuartcStreamDelegate(int id, std::string* read_buffer)
MockQuartcStreamDelegate(int id, QuicString* read_buffer)
: id_(id), read_buffer_(read_buffer) {}
void OnBufferChanged(QuartcStreamInterface* stream) override {
last_bytes_buffered_ = stream->bytes_buffered();
void OnBufferChanged(QuartcStream* stream) override {
last_bytes_buffered_ = stream->BufferedDataBytes();
}
void OnReceived(QuartcStreamInterface* stream,
void OnReceived(QuartcStream* stream,
const char* data,
size_t size) override {
EXPECT_EQ(id_, stream->stream_id());
EXPECT_EQ(id_, stream->id());
read_buffer_->append(data, size);
}
void OnClose(QuartcStreamInterface* stream) override { closed_ = true; }
void OnClose(QuartcStream* stream) override { closed_ = true; }
bool closed() { return closed_; }
......@@ -157,7 +156,7 @@ class MockQuartcStreamDelegate : public QuartcStreamInterface::Delegate {
protected:
uint32_t id_;
// Data read by the QuicStream.
std::string* read_buffer_;
QuicString* read_buffer_;
// Whether the QuicStream is closed.
bool closed_ = false;
......@@ -174,9 +173,7 @@ class QuartcStreamTest : public QuicTest, public QuicConnectionHelperInterface {
ip.FromString("0.0.0.0");
bool owns_writer = true;
// We only use QuartcFactory for its role as an alarm factory.
QuartcFactoryConfig config;
alarm_factory_ = QuicMakeUnique<QuartcFactory>(config);
alarm_factory_ = QuicMakeUnique<test::MockAlarmFactory>();
connection_ = QuicMakeUnique<QuicConnection>(
0, QuicSocketAddress(ip, 0), this /*QuicConnectionHelperInterface*/,
......@@ -210,9 +207,9 @@ class QuartcStreamTest : public QuicTest, public QuicConnectionHelperInterface {
std::unique_ptr<MockQuartcStreamDelegate> mock_stream_delegate_;
std::unique_ptr<MockQuicSession> session_;
// Data written by the ReliableQuicStreamAdapterTest.
std::string write_buffer_;
QuicString write_buffer_;
// Data read by the ReliableQuicStreamAdapterTest.
std::string read_buffer_;
QuicString read_buffer_;
std::unique_ptr<QuicAlarmFactory> alarm_factory_;
std::unique_ptr<QuicConnection> connection_;
// Used to implement the QuicConnectionHelperInterface.
......@@ -225,7 +222,7 @@ TEST_F(QuartcStreamTest, WriteDataWhole) {
CreateReliableQuicStream();
char message[] = "Foo bar";
test::QuicTestMemSliceVector data({std::make_pair(message, 7)});
stream_->Write(data.span(), kDefaultParam);
stream_->WriteMemSlices(data.span(), /*fin=*/false);
EXPECT_EQ("Foo bar", write_buffer_);
}
......@@ -234,7 +231,7 @@ TEST_F(QuartcStreamTest, WriteDataPartial) {
CreateReliableQuicStream();
char message[] = "Foo bar";
test::QuicTestMemSliceVector data({std::make_pair(message, 5)});
stream_->Write(data.span(), kDefaultParam);
stream_->WriteMemSlices(data.span(), /*fin=*/false);
EXPECT_EQ("Foo b", write_buffer_);
}
......@@ -247,11 +244,11 @@ TEST_F(QuartcStreamTest, StreamBuffersData) {
// The stream is not yet writable, so data will be buffered.
session_->set_writable(false);
stream_->Write(data.span(), kDefaultParam);
stream_->WriteMemSlices(data.span(), /*fin=*/false);
// Check that data is buffered.
EXPECT_TRUE(stream_->HasBufferedData());
EXPECT_EQ(7u, stream_->bytes_buffered());
EXPECT_EQ(7u, stream_->BufferedDataBytes());
// Check that the stream told its delegate about the buffer change.
EXPECT_EQ(7u, mock_stream_delegate_->last_bytes_buffered());
......@@ -265,10 +262,10 @@ TEST_F(QuartcStreamTest, StreamBuffersData) {
test::QuicTestMemSliceVector data1({std::make_pair(message1, 5)});
// More writes go into the buffer.
stream_->Write(data1.span(), kDefaultParam);
stream_->WriteMemSlices(data1.span(), /*fin=*/false);
EXPECT_TRUE(stream_->HasBufferedData());
EXPECT_EQ(12u, stream_->bytes_buffered());
EXPECT_EQ(12u, stream_->BufferedDataBytes());
EXPECT_EQ(12u, mock_stream_delegate_->last_bytes_buffered());
EXPECT_EQ(0ul, write_buffer_.size());
......@@ -277,7 +274,7 @@ TEST_F(QuartcStreamTest, StreamBuffersData) {
stream_->OnCanWrite();
EXPECT_FALSE(stream_->HasBufferedData());
EXPECT_EQ(0u, stream_->bytes_buffered());
EXPECT_EQ(0u, stream_->BufferedDataBytes());
EXPECT_EQ(0u, mock_stream_delegate_->last_bytes_buffered());
EXPECT_EQ("Foo barxyzzy", write_buffer_);
}
......@@ -317,10 +314,11 @@ TEST_F(QuartcStreamTest, ReadDataPartial) {
EXPECT_EQ("Hello", read_buffer_);
}
// Streams do not call OnReceived() after FinishReading().
TEST_F(QuartcStreamTest, FinishReading) {
// Streams do not call OnReceived() after StopReading().
// Note: this is tested here because Quartc relies on this behavior.
TEST_F(QuartcStreamTest, StopReading) {
CreateReliableQuicStream();
stream_->FinishReading();
stream_->StopReading();
QuicStreamFrame frame(kStreamId, false, 0, "Hello, World!");
stream_->OnStreamFrame(frame);
......@@ -348,10 +346,8 @@ TEST_F(QuartcStreamTest, CloseOnFins) {
QuicStreamFrame frame(kStreamId, true, 0, 0);
stream_->OnStreamFrame(frame);
QuartcStreamInterface::WriteParameters param;
param.fin = true;
test::QuicTestMemSliceVector data({});
stream_->Write(data.span(), param);
stream_->WriteMemSlices(data.span(), /*fin=*/true);
// Check that the OnClose() callback occurred.
EXPECT_TRUE(mock_stream_delegate_->closed());
......
// Copyright (c) 2017 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.
#ifndef NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_TASK_RUNNER_INTERFACE_H_
#define NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_TASK_RUNNER_INTERFACE_H_
#include <stdint.h>
#include <memory>
namespace quic {
// Used by platform specific QuicAlarms. For example, WebRTC will use it to set
// and cancel an alarm. When setting an alarm, the task runner will schedule a
// task on rtc::Thread. When canceling an alarm, the canceler for that task will
// be called.
class QuartcTaskRunnerInterface {
public:
virtual ~QuartcTaskRunnerInterface() {}
class Task {
public:
virtual ~Task() {}
// Called when it's time to start the task.
virtual void Run() = 0;
};
// A handler used to cancel a scheduled task. In some cases, a task cannot
// be directly canceled with its pointer. For example, in WebRTC, the task
// will be scheduled on rtc::Thread. When canceling a task, its pointer cannot
// locate the scheduled task in the thread message queue. So when scheduling a
// task, an additional handler (ScheduledTask) will be returned.
class ScheduledTask {
public:
virtual ~ScheduledTask() {}
// Cancels a scheduled task, meaning the task will not be run.
virtual void Cancel() = 0;
};
// Schedules a task, which will be run after the given delay. A ScheduledTask
// may be used to cancel the task.
virtual std::unique_ptr<ScheduledTask> Schedule(Task* task,
uint64_t delay_ms) = 0;
};
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_QUARTC_QUARTC_TASK_RUNNER_INTERFACE_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment