Reapply "dcsctp: Negotiate zero checksum"
The handover state has been added with commit daaa6ab5a8c74b87d9d6ded07342d8a2c50c73f7. This reverts commit 014cbed9d2377ec0a0b15f2c48b21a562f770366. Bug: webrtc:14997 Change-Id: Ie84f3184f3ea67aaa6438481634046ba18b497a6 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/320941 Reviewed-by: Florent Castelli <orphis@webrtc.org> Reviewed-by: Jeremy Leconte <jleconte@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40794}
This commit is contained in:
parent
63c50f59a6
commit
850296b7a4
@ -255,6 +255,10 @@ struct Metrics {
|
|||||||
// peers.
|
// peers.
|
||||||
bool uses_message_interleaving = false;
|
bool uses_message_interleaving = false;
|
||||||
|
|
||||||
|
// Indicates if draft-tuexen-tsvwg-sctp-zero-checksum-00 has been negotiated
|
||||||
|
// by both peers.
|
||||||
|
bool uses_zero_checksum = false;
|
||||||
|
|
||||||
// The number of negotiated incoming and outgoing streams, which is configured
|
// The number of negotiated incoming and outgoing streams, which is configured
|
||||||
// locally as `DcSctpOptions::announced_maximum_incoming_streams` and
|
// locally as `DcSctpOptions::announced_maximum_incoming_streams` and
|
||||||
// `DcSctpOptions::announced_maximum_outgoing_streams`, and which will be
|
// `DcSctpOptions::announced_maximum_outgoing_streams`, and which will be
|
||||||
|
|||||||
@ -21,6 +21,8 @@ struct Capabilities {
|
|||||||
bool message_interleaving = false;
|
bool message_interleaving = false;
|
||||||
// RFC6525 Stream Reconfiguration
|
// RFC6525 Stream Reconfiguration
|
||||||
bool reconfig = false;
|
bool reconfig = false;
|
||||||
|
// https://datatracker.ietf.org/doc/draft-tuexen-tsvwg-sctp-zero-checksum/
|
||||||
|
bool zero_checksum = false;
|
||||||
// Negotiated maximum incoming and outgoing stream count.
|
// Negotiated maximum incoming and outgoing stream count.
|
||||||
uint16_t negotiated_maximum_incoming_streams = 0;
|
uint16_t negotiated_maximum_incoming_streams = 0;
|
||||||
uint16_t negotiated_maximum_outgoing_streams = 0;
|
uint16_t negotiated_maximum_outgoing_streams = 0;
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
#include "net/dcsctp/packet/parameter/parameter.h"
|
#include "net/dcsctp/packet/parameter/parameter.h"
|
||||||
#include "net/dcsctp/packet/parameter/state_cookie_parameter.h"
|
#include "net/dcsctp/packet/parameter/state_cookie_parameter.h"
|
||||||
#include "net/dcsctp/packet/parameter/supported_extensions_parameter.h"
|
#include "net/dcsctp/packet/parameter/supported_extensions_parameter.h"
|
||||||
|
#include "net/dcsctp/packet/parameter/zero_checksum_acceptable_chunk_parameter.h"
|
||||||
#include "net/dcsctp/packet/sctp_packet.h"
|
#include "net/dcsctp/packet/sctp_packet.h"
|
||||||
#include "net/dcsctp/packet/tlv_trait.h"
|
#include "net/dcsctp/packet/tlv_trait.h"
|
||||||
#include "net/dcsctp/public/dcsctp_message.h"
|
#include "net/dcsctp/public/dcsctp_message.h"
|
||||||
@ -117,6 +118,11 @@ Capabilities ComputeCapabilities(const DcSctpOptions& options,
|
|||||||
capabilities.reconfig = true;
|
capabilities.reconfig = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.enable_zero_checksum &&
|
||||||
|
parameters.get<ZeroChecksumAcceptableChunkParameter>().has_value()) {
|
||||||
|
capabilities.zero_checksum = true;
|
||||||
|
}
|
||||||
|
|
||||||
capabilities.negotiated_maximum_incoming_streams = std::min(
|
capabilities.negotiated_maximum_incoming_streams = std::min(
|
||||||
options.announced_maximum_incoming_streams, peer_nbr_outbound_streams);
|
options.announced_maximum_incoming_streams, peer_nbr_outbound_streams);
|
||||||
capabilities.negotiated_maximum_outgoing_streams = std::min(
|
capabilities.negotiated_maximum_outgoing_streams = std::min(
|
||||||
@ -137,6 +143,9 @@ void AddCapabilityParameters(const DcSctpOptions& options,
|
|||||||
chunk_types.push_back(IDataChunk::kType);
|
chunk_types.push_back(IDataChunk::kType);
|
||||||
chunk_types.push_back(IForwardTsnChunk::kType);
|
chunk_types.push_back(IForwardTsnChunk::kType);
|
||||||
}
|
}
|
||||||
|
if (options.enable_zero_checksum) {
|
||||||
|
builder.Add(ZeroChecksumAcceptableChunkParameter());
|
||||||
|
}
|
||||||
builder.Add(SupportedExtensionsParameter(std::move(chunk_types)));
|
builder.Add(SupportedExtensionsParameter(std::move(chunk_types)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +288,10 @@ void DcSctpSocket::SendInit() {
|
|||||||
connect_params_.initial_tsn, params_builder.Build());
|
connect_params_.initial_tsn, params_builder.Build());
|
||||||
SctpPacket::Builder b(VerificationTag(0), options_);
|
SctpPacket::Builder b(VerificationTag(0), options_);
|
||||||
b.Add(init);
|
b.Add(init);
|
||||||
packet_sender_.Send(b);
|
// https://www.ietf.org/archive/id/draft-tuexen-tsvwg-sctp-zero-checksum-01.html#section-4.2
|
||||||
|
// "When an end point sends a packet containing an INIT chunk, it MUST include
|
||||||
|
// a correct CRC32c checksum in the packet containing the INIT chunk."
|
||||||
|
packet_sender_.Send(b, /*write_checksum=*/true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DcSctpSocket::MakeConnectionParameters() {
|
void DcSctpSocket::MakeConnectionParameters() {
|
||||||
@ -320,6 +332,7 @@ void DcSctpSocket::CreateTransmissionControlBlock(
|
|||||||
size_t a_rwnd,
|
size_t a_rwnd,
|
||||||
TieTag tie_tag) {
|
TieTag tie_tag) {
|
||||||
metrics_.uses_message_interleaving = capabilities.message_interleaving;
|
metrics_.uses_message_interleaving = capabilities.message_interleaving;
|
||||||
|
metrics_.uses_zero_checksum = capabilities.zero_checksum;
|
||||||
metrics_.negotiated_maximum_incoming_streams =
|
metrics_.negotiated_maximum_incoming_streams =
|
||||||
capabilities.negotiated_maximum_incoming_streams;
|
capabilities.negotiated_maximum_incoming_streams;
|
||||||
metrics_.negotiated_maximum_outgoing_streams =
|
metrics_.negotiated_maximum_outgoing_streams =
|
||||||
@ -351,6 +364,7 @@ void DcSctpSocket::RestoreFromState(const DcSctpSocketHandoverState& state) {
|
|||||||
capabilities.message_interleaving =
|
capabilities.message_interleaving =
|
||||||
state.capabilities.message_interleaving;
|
state.capabilities.message_interleaving;
|
||||||
capabilities.reconfig = state.capabilities.reconfig;
|
capabilities.reconfig = state.capabilities.reconfig;
|
||||||
|
capabilities.zero_checksum = state.capabilities.zero_checksum;
|
||||||
capabilities.negotiated_maximum_incoming_streams =
|
capabilities.negotiated_maximum_incoming_streams =
|
||||||
state.capabilities.negotiated_maximum_incoming_streams;
|
state.capabilities.negotiated_maximum_incoming_streams;
|
||||||
capabilities.negotiated_maximum_outgoing_streams =
|
capabilities.negotiated_maximum_outgoing_streams =
|
||||||
@ -1211,7 +1225,9 @@ void DcSctpSocket::HandleInit(const CommonHeader& header,
|
|||||||
options_.announced_maximum_incoming_streams,
|
options_.announced_maximum_incoming_streams,
|
||||||
connect_params_.initial_tsn, params_builder.Build());
|
connect_params_.initial_tsn, params_builder.Build());
|
||||||
b.Add(init_ack);
|
b.Add(init_ack);
|
||||||
packet_sender_.Send(b);
|
// If the peer has signaled that it supports zero checksum, INIT-ACK can then
|
||||||
|
// have its checksum as zero.
|
||||||
|
packet_sender_.Send(b, /*write_checksum=*/!capabilities.zero_checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DcSctpSocket::HandleInitAck(
|
void DcSctpSocket::HandleInitAck(
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
#include "net/dcsctp/common/handover_testing.h"
|
#include "net/dcsctp/common/handover_testing.h"
|
||||||
#include "net/dcsctp/common/math.h"
|
#include "net/dcsctp/common/math.h"
|
||||||
#include "net/dcsctp/packet/chunk/chunk.h"
|
#include "net/dcsctp/packet/chunk/chunk.h"
|
||||||
|
#include "net/dcsctp/packet/chunk/cookie_ack_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
|
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/data_chunk.h"
|
#include "net/dcsctp/packet/chunk/data_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/data_common.h"
|
#include "net/dcsctp/packet/chunk/data_common.h"
|
||||||
@ -31,6 +32,7 @@
|
|||||||
#include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
|
#include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
|
#include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/idata_chunk.h"
|
#include "net/dcsctp/packet/chunk/idata_chunk.h"
|
||||||
|
#include "net/dcsctp/packet/chunk/init_ack_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/init_chunk.h"
|
#include "net/dcsctp/packet/chunk/init_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/sack_chunk.h"
|
#include "net/dcsctp/packet/chunk/sack_chunk.h"
|
||||||
#include "net/dcsctp/packet/chunk/shutdown_chunk.h"
|
#include "net/dcsctp/packet/chunk/shutdown_chunk.h"
|
||||||
@ -59,8 +61,10 @@ namespace {
|
|||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::AllOf;
|
using ::testing::AllOf;
|
||||||
using ::testing::ElementsAre;
|
using ::testing::ElementsAre;
|
||||||
|
using ::testing::Eq;
|
||||||
using ::testing::HasSubstr;
|
using ::testing::HasSubstr;
|
||||||
using ::testing::IsEmpty;
|
using ::testing::IsEmpty;
|
||||||
|
using ::testing::Not;
|
||||||
using ::testing::SizeIs;
|
using ::testing::SizeIs;
|
||||||
using ::testing::UnorderedElementsAre;
|
using ::testing::UnorderedElementsAre;
|
||||||
|
|
||||||
@ -2903,5 +2907,156 @@ TEST(DcSctpSocketTest, ResetStreamsWithPausedSenderResumesWhenPerformed) {
|
|||||||
EXPECT_EQ(msg2->payload().size(), kSmallMessageSize);
|
EXPECT_EQ(msg2->payload().size(), kSmallMessageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_P(DcSctpSocketParametrizedTest, ZeroChecksumMetricsAreSet) {
|
||||||
|
std::vector<std::pair<bool, bool>> combinations = {
|
||||||
|
{false, false}, {false, true}, {true, false}, {true, true}};
|
||||||
|
for (const auto& [a_enable, z_enable] : combinations) {
|
||||||
|
DcSctpOptions a_options = {.enable_zero_checksum = a_enable};
|
||||||
|
DcSctpOptions z_options = {.enable_zero_checksum = z_enable};
|
||||||
|
|
||||||
|
SocketUnderTest a("A", a_options);
|
||||||
|
auto z = std::make_unique<SocketUnderTest>("Z", z_options);
|
||||||
|
|
||||||
|
ConnectSockets(a, *z);
|
||||||
|
z = MaybeHandoverSocket(std::move(z));
|
||||||
|
|
||||||
|
EXPECT_EQ(a.socket.GetMetrics()->uses_zero_checksum, a_enable && z_enable);
|
||||||
|
EXPECT_EQ(z->socket.GetMetrics()->uses_zero_checksum, a_enable && z_enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DcSctpSocketTest, AlwaysSendsInitWithNonZeroChecksum) {
|
||||||
|
DcSctpOptions options = {.enable_zero_checksum = true};
|
||||||
|
SocketUnderTest a("A", options);
|
||||||
|
|
||||||
|
a.socket.Connect();
|
||||||
|
std::vector<uint8_t> data = a.cb.ConsumeSentPacket();
|
||||||
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
||||||
|
SctpPacket::Parse(data, options));
|
||||||
|
EXPECT_THAT(packet.descriptors(),
|
||||||
|
ElementsAre(testing::Field(&SctpPacket::ChunkDescriptor::type,
|
||||||
|
InitChunk::kType)));
|
||||||
|
EXPECT_THAT(packet.common_header().checksum, Not(Eq(0u)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DcSctpSocketTest, MaySendInitAckWithZeroChecksum) {
|
||||||
|
DcSctpOptions options = {.enable_zero_checksum = true};
|
||||||
|
SocketUnderTest a("A", options);
|
||||||
|
SocketUnderTest z("Z", options);
|
||||||
|
|
||||||
|
a.socket.Connect();
|
||||||
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // INIT
|
||||||
|
|
||||||
|
std::vector<uint8_t> data = z.cb.ConsumeSentPacket();
|
||||||
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
||||||
|
SctpPacket::Parse(data, options));
|
||||||
|
EXPECT_THAT(packet.descriptors(),
|
||||||
|
ElementsAre(testing::Field(&SctpPacket::ChunkDescriptor::type,
|
||||||
|
InitAckChunk::kType)));
|
||||||
|
EXPECT_THAT(packet.common_header().checksum, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DcSctpSocketTest, AlwaysSendsCookieEchoWithNonZeroChecksum) {
|
||||||
|
DcSctpOptions options = {.enable_zero_checksum = true};
|
||||||
|
SocketUnderTest a("A", options);
|
||||||
|
SocketUnderTest z("Z", options);
|
||||||
|
|
||||||
|
a.socket.Connect();
|
||||||
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // INIT
|
||||||
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket()); // INIT-ACK
|
||||||
|
|
||||||
|
std::vector<uint8_t> data = a.cb.ConsumeSentPacket();
|
||||||
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
||||||
|
SctpPacket::Parse(data, options));
|
||||||
|
EXPECT_THAT(packet.descriptors(),
|
||||||
|
ElementsAre(testing::Field(&SctpPacket::ChunkDescriptor::type,
|
||||||
|
CookieEchoChunk::kType)));
|
||||||
|
EXPECT_THAT(packet.common_header().checksum, Not(Eq(0u)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DcSctpSocketTest, SendsCookieAckWithZeroChecksum) {
|
||||||
|
DcSctpOptions options = {.enable_zero_checksum = true};
|
||||||
|
SocketUnderTest a("A", options);
|
||||||
|
SocketUnderTest z("Z", options);
|
||||||
|
|
||||||
|
a.socket.Connect();
|
||||||
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // INIT
|
||||||
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket()); // INIT-ACK
|
||||||
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // COOKIE-ECHO
|
||||||
|
|
||||||
|
std::vector<uint8_t> data = z.cb.ConsumeSentPacket();
|
||||||
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
||||||
|
SctpPacket::Parse(data, options));
|
||||||
|
EXPECT_THAT(packet.descriptors(),
|
||||||
|
ElementsAre(testing::Field(&SctpPacket::ChunkDescriptor::type,
|
||||||
|
CookieAckChunk::kType)));
|
||||||
|
EXPECT_THAT(packet.common_header().checksum, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DcSctpSocketParametrizedTest, SendsDataWithZeroChecksum) {
|
||||||
|
DcSctpOptions options = {.enable_zero_checksum = true};
|
||||||
|
SocketUnderTest a("A", options);
|
||||||
|
auto z = std::make_unique<SocketUnderTest>("Z", options);
|
||||||
|
|
||||||
|
ConnectSockets(a, *z);
|
||||||
|
z = MaybeHandoverSocket(std::move(z));
|
||||||
|
|
||||||
|
std::vector<uint8_t> payload(a.options.mtu - 100);
|
||||||
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
||||||
|
|
||||||
|
std::vector<uint8_t> data = a.cb.ConsumeSentPacket();
|
||||||
|
z->socket.ReceivePacket(data);
|
||||||
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
||||||
|
SctpPacket::Parse(data, options));
|
||||||
|
EXPECT_THAT(packet.descriptors(),
|
||||||
|
ElementsAre(testing::Field(&SctpPacket::ChunkDescriptor::type,
|
||||||
|
DataChunk::kType)));
|
||||||
|
EXPECT_THAT(packet.common_header().checksum, 0u);
|
||||||
|
|
||||||
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DcSctpSocketParametrizedTest, AllPacketsAfterConnectHaveZeroChecksum) {
|
||||||
|
DcSctpOptions options = {.enable_zero_checksum = true};
|
||||||
|
SocketUnderTest a("A", options);
|
||||||
|
auto z = std::make_unique<SocketUnderTest>("Z", options);
|
||||||
|
|
||||||
|
ConnectSockets(a, *z);
|
||||||
|
z = MaybeHandoverSocket(std::move(z));
|
||||||
|
|
||||||
|
// Send large messages in both directions, and verify that they arrive and
|
||||||
|
// that every packet has zero checksum.
|
||||||
|
std::vector<uint8_t> payload(kLargeMessageSize);
|
||||||
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions);
|
||||||
|
z->socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (auto data = a.cb.ConsumeSentPacket(); !data.empty()) {
|
||||||
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
||||||
|
SctpPacket::Parse(data, options));
|
||||||
|
EXPECT_THAT(packet.common_header().checksum, 0u);
|
||||||
|
z->socket.ReceivePacket(std::move(data));
|
||||||
|
|
||||||
|
} else if (auto data = z->cb.ConsumeSentPacket(); !data.empty()) {
|
||||||
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
||||||
|
SctpPacket::Parse(data, options));
|
||||||
|
EXPECT_THAT(packet.common_header().checksum, 0u);
|
||||||
|
a.socket.ReceivePacket(std::move(data));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<DcSctpMessage> msg1 = z->cb.ConsumeReceivedMessage();
|
||||||
|
ASSERT_TRUE(msg1.has_value());
|
||||||
|
EXPECT_THAT(msg1->payload(), SizeIs(kLargeMessageSize));
|
||||||
|
|
||||||
|
absl::optional<DcSctpMessage> msg2 = a.cb.ConsumeReceivedMessage();
|
||||||
|
ASSERT_TRUE(msg2.has_value());
|
||||||
|
EXPECT_THAT(msg2->payload(), SizeIs(kLargeMessageSize));
|
||||||
|
|
||||||
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace dcsctp
|
} // namespace dcsctp
|
||||||
|
|||||||
@ -21,12 +21,12 @@ PacketSender::PacketSender(DcSctpSocketCallbacks& callbacks,
|
|||||||
SendPacketStatus)> on_sent_packet)
|
SendPacketStatus)> on_sent_packet)
|
||||||
: callbacks_(callbacks), on_sent_packet_(std::move(on_sent_packet)) {}
|
: callbacks_(callbacks), on_sent_packet_(std::move(on_sent_packet)) {}
|
||||||
|
|
||||||
bool PacketSender::Send(SctpPacket::Builder& builder) {
|
bool PacketSender::Send(SctpPacket::Builder& builder, bool write_checksum) {
|
||||||
if (builder.empty()) {
|
if (builder.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> payload = builder.Build();
|
std::vector<uint8_t> payload = builder.Build(write_checksum);
|
||||||
|
|
||||||
SendPacketStatus status = callbacks_.SendPacketWithStatus(payload);
|
SendPacketStatus status = callbacks_.SendPacketWithStatus(payload);
|
||||||
on_sent_packet_(payload, status);
|
on_sent_packet_(payload, status);
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class PacketSender {
|
|||||||
SendPacketStatus)> on_sent_packet);
|
SendPacketStatus)> on_sent_packet);
|
||||||
|
|
||||||
// Sends the packet, and returns true if it was sent successfully.
|
// Sends the packet, and returns true if it was sent successfully.
|
||||||
bool Send(SctpPacket::Builder& builder);
|
bool Send(SctpPacket::Builder& builder, bool write_checksum = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DcSctpSocketCallbacks& callbacks_;
|
DcSctpSocketCallbacks& callbacks_;
|
||||||
|
|||||||
@ -42,6 +42,7 @@ std::vector<uint8_t> StateCookie::Serialize() {
|
|||||||
buffer.Store8<30>(capabilities_.reconfig);
|
buffer.Store8<30>(capabilities_.reconfig);
|
||||||
buffer.Store16<32>(capabilities_.negotiated_maximum_incoming_streams);
|
buffer.Store16<32>(capabilities_.negotiated_maximum_incoming_streams);
|
||||||
buffer.Store16<34>(capabilities_.negotiated_maximum_outgoing_streams);
|
buffer.Store16<34>(capabilities_.negotiated_maximum_outgoing_streams);
|
||||||
|
buffer.Store8<36>(capabilities_.zero_checksum);
|
||||||
return cookie;
|
return cookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +75,7 @@ absl::optional<StateCookie> StateCookie::Deserialize(
|
|||||||
capabilities.reconfig = buffer.Load8<30>() != 0;
|
capabilities.reconfig = buffer.Load8<30>() != 0;
|
||||||
capabilities.negotiated_maximum_incoming_streams = buffer.Load16<32>();
|
capabilities.negotiated_maximum_incoming_streams = buffer.Load16<32>();
|
||||||
capabilities.negotiated_maximum_outgoing_streams = buffer.Load16<34>();
|
capabilities.negotiated_maximum_outgoing_streams = buffer.Load16<34>();
|
||||||
|
capabilities.zero_checksum = buffer.Load8<36>() != 0;
|
||||||
|
|
||||||
return StateCookie(verification_tag, initial_tsn, a_rwnd, tie_tag,
|
return StateCookie(verification_tag, initial_tsn, a_rwnd, tie_tag,
|
||||||
capabilities);
|
capabilities);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ namespace dcsctp {
|
|||||||
// Do not trust anything in it; no pointers or anything like that.
|
// Do not trust anything in it; no pointers or anything like that.
|
||||||
class StateCookie {
|
class StateCookie {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t kCookieSize = 36;
|
static constexpr size_t kCookieSize = 37;
|
||||||
|
|
||||||
StateCookie(VerificationTag initiate_tag,
|
StateCookie(VerificationTag initiate_tag,
|
||||||
TSN initial_tsn,
|
TSN initial_tsn,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ TEST(StateCookieTest, SerializeAndDeserialize) {
|
|||||||
Capabilities capabilities = {.partial_reliability = true,
|
Capabilities capabilities = {.partial_reliability = true,
|
||||||
.message_interleaving = false,
|
.message_interleaving = false,
|
||||||
.reconfig = true,
|
.reconfig = true,
|
||||||
|
.zero_checksum = true,
|
||||||
.negotiated_maximum_incoming_streams = 123,
|
.negotiated_maximum_incoming_streams = 123,
|
||||||
.negotiated_maximum_outgoing_streams = 234};
|
.negotiated_maximum_outgoing_streams = 234};
|
||||||
StateCookie cookie(VerificationTag(123), TSN(456),
|
StateCookie cookie(VerificationTag(123), TSN(456),
|
||||||
@ -36,6 +37,7 @@ TEST(StateCookieTest, SerializeAndDeserialize) {
|
|||||||
EXPECT_TRUE(deserialized.capabilities().partial_reliability);
|
EXPECT_TRUE(deserialized.capabilities().partial_reliability);
|
||||||
EXPECT_FALSE(deserialized.capabilities().message_interleaving);
|
EXPECT_FALSE(deserialized.capabilities().message_interleaving);
|
||||||
EXPECT_TRUE(deserialized.capabilities().reconfig);
|
EXPECT_TRUE(deserialized.capabilities().reconfig);
|
||||||
|
EXPECT_TRUE(deserialized.capabilities().zero_checksum);
|
||||||
EXPECT_EQ(deserialized.capabilities().negotiated_maximum_incoming_streams,
|
EXPECT_EQ(deserialized.capabilities().negotiated_maximum_incoming_streams,
|
||||||
123);
|
123);
|
||||||
EXPECT_EQ(deserialized.capabilities().negotiated_maximum_outgoing_streams,
|
EXPECT_EQ(deserialized.capabilities().negotiated_maximum_outgoing_streams,
|
||||||
|
|||||||
@ -163,7 +163,7 @@ void TransmissionControlBlock::MaybeSendForwardTsn(SctpPacket::Builder& builder,
|
|||||||
} else {
|
} else {
|
||||||
builder.Add(retransmission_queue_.CreateForwardTsn());
|
builder.Add(retransmission_queue_.CreateForwardTsn());
|
||||||
}
|
}
|
||||||
packet_sender_.Send(builder);
|
Send(builder);
|
||||||
// https://datatracker.ietf.org/doc/html/rfc3758
|
// https://datatracker.ietf.org/doc/html/rfc3758
|
||||||
// "IMPLEMENTATION NOTE: An implementation may wish to limit the number of
|
// "IMPLEMENTATION NOTE: An implementation may wish to limit the number of
|
||||||
// duplicate FORWARD TSN chunks it sends by ... waiting a full RTT before
|
// duplicate FORWARD TSN chunks it sends by ... waiting a full RTT before
|
||||||
@ -198,7 +198,7 @@ void TransmissionControlBlock::MaybeSendFastRetransmit() {
|
|||||||
builder.Add(DataChunk(tsn, std::move(data), false));
|
builder.Add(DataChunk(tsn, std::move(data), false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packet_sender_.Send(builder);
|
Send(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransmissionControlBlock::SendBufferedPackets(SctpPacket::Builder& builder,
|
void TransmissionControlBlock::SendBufferedPackets(SctpPacket::Builder& builder,
|
||||||
@ -245,7 +245,13 @@ void TransmissionControlBlock::SendBufferedPackets(SctpPacket::Builder& builder,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!packet_sender_.Send(builder)) {
|
// https://www.ietf.org/archive/id/draft-tuexen-tsvwg-sctp-zero-checksum-02.html#section-4.2
|
||||||
|
// "When an end point sends a packet containing a COOKIE ECHO chunk, it MUST
|
||||||
|
// include a correct CRC32c checksum in the packet containing the COOKIE
|
||||||
|
// ECHO chunk."
|
||||||
|
bool write_checksum =
|
||||||
|
!capabilities_.zero_checksum || cookie_echo_chunk_.has_value();
|
||||||
|
if (!packet_sender_.Send(builder, write_checksum)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +280,9 @@ std::string TransmissionControlBlock::ToString() const {
|
|||||||
if (capabilities_.reconfig) {
|
if (capabilities_.reconfig) {
|
||||||
sb << "Reconfig,";
|
sb << "Reconfig,";
|
||||||
}
|
}
|
||||||
|
if (capabilities_.zero_checksum) {
|
||||||
|
sb << "ZeroChecksum,";
|
||||||
|
}
|
||||||
sb << " max_in=" << capabilities_.negotiated_maximum_incoming_streams;
|
sb << " max_in=" << capabilities_.negotiated_maximum_incoming_streams;
|
||||||
sb << " max_out=" << capabilities_.negotiated_maximum_outgoing_streams;
|
sb << " max_out=" << capabilities_.negotiated_maximum_outgoing_streams;
|
||||||
|
|
||||||
@ -294,6 +303,7 @@ void TransmissionControlBlock::AddHandoverState(
|
|||||||
state.capabilities.partial_reliability = capabilities_.partial_reliability;
|
state.capabilities.partial_reliability = capabilities_.partial_reliability;
|
||||||
state.capabilities.message_interleaving = capabilities_.message_interleaving;
|
state.capabilities.message_interleaving = capabilities_.message_interleaving;
|
||||||
state.capabilities.reconfig = capabilities_.reconfig;
|
state.capabilities.reconfig = capabilities_.reconfig;
|
||||||
|
state.capabilities.zero_checksum = capabilities_.zero_checksum;
|
||||||
state.capabilities.negotiated_maximum_incoming_streams =
|
state.capabilities.negotiated_maximum_incoming_streams =
|
||||||
capabilities_.negotiated_maximum_incoming_streams;
|
capabilities_.negotiated_maximum_incoming_streams;
|
||||||
state.capabilities.negotiated_maximum_outgoing_streams =
|
state.capabilities.negotiated_maximum_outgoing_streams =
|
||||||
|
|||||||
@ -80,7 +80,8 @@ class TransmissionControlBlock : public Context {
|
|||||||
return tx_error_counter_.IsExhausted();
|
return tx_error_counter_.IsExhausted();
|
||||||
}
|
}
|
||||||
void Send(SctpPacket::Builder& builder) override {
|
void Send(SctpPacket::Builder& builder) override {
|
||||||
packet_sender_.Send(builder);
|
packet_sender_.Send(builder,
|
||||||
|
/*write_checksum=*/!capabilities_.zero_checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other accessors
|
// Other accessors
|
||||||
|
|||||||
@ -92,6 +92,7 @@ TEST_F(TransmissionControlBlockTest, LogsAllCapabilitiesInToSring) {
|
|||||||
capabilities_.negotiated_maximum_outgoing_streams = 2000;
|
capabilities_.negotiated_maximum_outgoing_streams = 2000;
|
||||||
capabilities_.message_interleaving = true;
|
capabilities_.message_interleaving = true;
|
||||||
capabilities_.partial_reliability = true;
|
capabilities_.partial_reliability = true;
|
||||||
|
capabilities_.zero_checksum = true;
|
||||||
capabilities_.reconfig = true;
|
capabilities_.reconfig = true;
|
||||||
|
|
||||||
TransmissionControlBlock tcb(
|
TransmissionControlBlock tcb(
|
||||||
@ -99,9 +100,10 @@ TEST_F(TransmissionControlBlockTest, LogsAllCapabilitiesInToSring) {
|
|||||||
kMyVerificationTag, kMyInitialTsn, kPeerVerificationTag, kPeerInitialTsn,
|
kMyVerificationTag, kMyInitialTsn, kPeerVerificationTag, kPeerInitialTsn,
|
||||||
kArwnd, kTieTag, sender_, on_connection_established.AsStdFunction());
|
kArwnd, kTieTag, sender_, on_connection_established.AsStdFunction());
|
||||||
|
|
||||||
EXPECT_EQ(tcb.ToString(),
|
EXPECT_EQ(
|
||||||
|
tcb.ToString(),
|
||||||
"verification_tag=000001c8, last_cumulative_ack=999, "
|
"verification_tag=000001c8, last_cumulative_ack=999, "
|
||||||
"capabilities=PR,IL,Reconfig, max_in=1000 max_out=2000");
|
"capabilities=PR,IL,Reconfig,ZeroChecksum, max_in=1000 max_out=2000");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TransmissionControlBlockTest, IsInitiallyHandoverReady) {
|
TEST_F(TransmissionControlBlockTest, IsInitiallyHandoverReady) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user