dcsctp: Persist all state in state cookie

In the example below, the association is being established between peer
A and Z, and A is the initiating party.

Before this CL, when an association was about to be established, Z would
after having received the INIT chunk, persist state in the socket about
which verification tag and initial TSN that was picked. These would be
re-generated on every incoming INIT (that's fine), but when A had
extracted the cookie from INIT_ACK and sent a reply (COOKIE_ECHO) with
the state cookie, that could fail validation when it's received by Z, if
the sent cookie was not the most recent one or if the COOKIE_ECHO had a
verification tag coming not from the most recent INIT_ACK, because Z had
replaced the state in the socket with the one generated when the second
INIT_ACK chunk was generated - state it used for validation of future
received data.

In other words:
A -> INIT 1
<timeout>
A -> INIT 2 (retransmission of INIT 1)
INIT 1 -> Z - sends INIT_ACK 1 with verification_tag=1, initial_tsn=1,
              cookie 1 (and records these to socket state)
INIT 2 -> Z - sends INIT_ACK 2 with verification_tag=2, initial_tsn=2,
              cookie 2 (replaces socket state with the new data)
INIT_ACK 1 -> A -> sends COOKIE_ECHO with verification_tag=1, cookie 1
COOKIE_ECHO (cookie 1) -> Z <FAILS, as the state isn't as expected>.

The solution is really to do what RFC4960 says, to not maintain any
state as the receiving peer until COOKIE_ECHO has been received. This
was initially not done because the underlying reason why this is
important in SCTP is to avoid denial of service, and this is why SCTP
has the four-way handshake. But for Data Channels - SCTP over DTLS -
this attack vector isn't available. So the implementation was
"simplified" by keeping socket state instead of encoding it in the
state cookie, but that obviously had downsides.

So with this CL, the non-initiating peer in connection establishment
doesn't keep any socket state, and puts all that state in the state
cookie instead. This allows any COOKIE_ECHO to be received by Z.

Bug: webrtc:15712
Change-Id: I596c7330ce27292612d3c9f86b21c712f6f4e408
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/330440
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#41340}
diff --git a/net/dcsctp/socket/dcsctp_socket.cc b/net/dcsctp/socket/dcsctp_socket.cc
index f0f9590..5fc9bf5 100644
--- a/net/dcsctp/socket/dcsctp_socket.cc
+++ b/net/dcsctp/socket/dcsctp_socket.cc
@@ -296,20 +296,15 @@
   packet_sender_.Send(b, /*write_checksum=*/true);
 }
 
-void DcSctpSocket::MakeConnectionParameters() {
-  VerificationTag new_verification_tag(
-      callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
-  TSN initial_tsn(callbacks_.GetRandomInt(kMinInitialTsn, kMaxInitialTsn));
-  connect_params_.initial_tsn = initial_tsn;
-  connect_params_.verification_tag = new_verification_tag;
-}
-
 void DcSctpSocket::Connect() {
   RTC_DCHECK_RUN_ON(&thread_checker_);
   CallbackDeferrer::ScopedDeferrer deferrer(callbacks_);
 
   if (state_ == State::kClosed) {
-    MakeConnectionParameters();
+    connect_params_.initial_tsn =
+        TSN(callbacks_.GetRandomInt(kMinInitialTsn, kMaxInitialTsn));
+    connect_params_.verification_tag = VerificationTag(
+        callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
     RTC_DLOG(LS_INFO)
         << log_prefix()
         << rtc::StringFormat(
@@ -1153,11 +1148,16 @@
   }
 
   TieTag tie_tag(0);
+  VerificationTag my_verification_tag;
+  TSN my_initial_tsn;
   if (state_ == State::kClosed) {
     RTC_DLOG(LS_VERBOSE) << log_prefix()
                          << "Received Init in closed state (normal)";
 
-    MakeConnectionParameters();
+    my_verification_tag = VerificationTag(
+        callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
+    my_initial_tsn =
+        TSN(callbacks_.GetRandomInt(kMinInitialTsn, kMaxInitialTsn));
   } else if (state_ == State::kCookieWait || state_ == State::kCookieEchoed) {
     // https://tools.ietf.org/html/rfc4960#section-5.2.1
     // "This usually indicates an initialization collision, i.e., each
@@ -1170,6 +1170,8 @@
     // endpoint) was sent."
     RTC_DLOG(LS_VERBOSE) << log_prefix()
                          << "Received Init indicating simultaneous connections";
+    my_verification_tag = connect_params_.verification_tag;
+    my_initial_tsn = connect_params_.initial_tsn;
   } else {
     RTC_DCHECK(tcb_ != nullptr);
     // https://tools.ietf.org/html/rfc4960#section-5.2.2
@@ -1184,17 +1186,16 @@
                          << "Received Init indicating restarted connection";
     // Create a new verification tag - different from the previous one.
     for (int tries = 0; tries < 10; ++tries) {
-      connect_params_.verification_tag = VerificationTag(
+      my_verification_tag = VerificationTag(
           callbacks_.GetRandomInt(kMinVerificationTag, kMaxVerificationTag));
-      if (connect_params_.verification_tag != tcb_->my_verification_tag()) {
+      if (my_verification_tag != tcb_->my_verification_tag()) {
         break;
       }
     }
 
     // Make the initial TSN make a large jump, so that there is no overlap
     // with the old and new association.
-    connect_params_.initial_tsn =
-        TSN(*tcb_->retransmission_queue().next_tsn() + 1000000);
+    my_initial_tsn = TSN(*tcb_->retransmission_queue().next_tsn() + 1000000);
     tie_tag = tcb_->tie_tag();
   }
 
@@ -1204,8 +1205,8 @@
              "Proceeding with connection. my_verification_tag=%08x, "
              "my_initial_tsn=%u, peer_verification_tag=%08x, "
              "peer_initial_tsn=%u",
-             *connect_params_.verification_tag, *connect_params_.initial_tsn,
-             *chunk->initiate_tag(), *chunk->initial_tsn());
+             *my_verification_tag, *my_initial_tsn, *chunk->initiate_tag(),
+             *chunk->initial_tsn());
 
   Capabilities capabilities =
       ComputeCapabilities(options_, chunk->nbr_outbound_streams(),
@@ -1214,16 +1215,17 @@
   SctpPacket::Builder b(chunk->initiate_tag(), options_);
   Parameters::Builder params_builder =
       Parameters::Builder().Add(StateCookieParameter(
-          StateCookie(chunk->initiate_tag(), chunk->initial_tsn(),
-                      chunk->a_rwnd(), tie_tag, capabilities)
+          StateCookie(chunk->initiate_tag(), my_verification_tag,
+                      chunk->initial_tsn(), my_initial_tsn, chunk->a_rwnd(),
+                      tie_tag, capabilities)
               .Serialize()));
   AddCapabilityParameters(options_, params_builder);
 
-  InitAckChunk init_ack(/*initiate_tag=*/connect_params_.verification_tag,
+  InitAckChunk init_ack(/*initiate_tag=*/my_verification_tag,
                         options_.max_receiver_window_buffer_size,
                         options_.announced_maximum_outgoing_streams,
                         options_.announced_maximum_incoming_streams,
-                        connect_params_.initial_tsn, params_builder.Build());
+                        my_initial_tsn, params_builder.Build());
   b.Add(init_ack);
   // If the peer has signaled that it supports zero checksum, INIT-ACK can then
   // have its checksum as zero.
@@ -1309,13 +1311,13 @@
       return;
     }
   } else {
-    if (header.verification_tag != connect_params_.verification_tag) {
+    if (header.verification_tag != cookie->my_tag()) {
       callbacks_.OnError(
           ErrorKind::kParseFailed,
           rtc::StringFormat(
               "Received CookieEcho with invalid verification tag: %08x, "
               "expected %08x",
-              *header.verification_tag, *connect_params_.verification_tag));
+              *header.verification_tag, *cookie->my_tag()));
       return;
     }
   }
@@ -1340,10 +1342,10 @@
     // send queue is already re-configured, and shouldn't be reset.
     send_queue_.Reset();
 
-    CreateTransmissionControlBlock(
-        cookie->capabilities(), connect_params_.verification_tag,
-        connect_params_.initial_tsn, cookie->initiate_tag(),
-        cookie->initial_tsn(), cookie->a_rwnd(), MakeTieTag(callbacks_));
+    CreateTransmissionControlBlock(cookie->capabilities(), cookie->my_tag(),
+                                   cookie->my_initial_tsn(), cookie->peer_tag(),
+                                   cookie->peer_initial_tsn(), cookie->a_rwnd(),
+                                   MakeTieTag(callbacks_));
   }
 
   SctpPacket::Builder b = tcb_->PacketBuilder();
@@ -1363,13 +1365,13 @@
                        << *tcb_->my_verification_tag()
                        << ", peer_tag=" << *header.verification_tag
                        << ", tcb_tag=" << *tcb_->peer_verification_tag()
-                       << ", cookie_tag=" << *cookie.initiate_tag()
+                       << ", peer_tag=" << *cookie.peer_tag()
                        << ", local_tie_tag=" << *tcb_->tie_tag()
                        << ", peer_tie_tag=" << *cookie.tie_tag();
   // https://tools.ietf.org/html/rfc4960#section-5.2.4
   // "Handle a COOKIE ECHO when a TCB Exists"
   if (header.verification_tag != tcb_->my_verification_tag() &&
-      tcb_->peer_verification_tag() != cookie.initiate_tag() &&
+      tcb_->peer_verification_tag() != cookie.peer_tag() &&
       cookie.tie_tag() == tcb_->tie_tag()) {
     // "A) In this case, the peer may have restarted."
     if (state_ == State::kShutdownAckSent) {
@@ -1377,7 +1379,7 @@
       // that the peer has restarted ...  it MUST NOT set up a new association
       // but instead resend the SHUTDOWN ACK and send an ERROR chunk with a
       // "Cookie Received While Shutting Down" error cause to its peer."
-      SctpPacket::Builder b(cookie.initiate_tag(), options_);
+      SctpPacket::Builder b(cookie.peer_tag(), options_);
       b.Add(ShutdownAckChunk());
       b.Add(ErrorChunk(Parameters::Builder()
                            .Add(CookieReceivedWhileShuttingDownCause())
@@ -1394,7 +1396,7 @@
     tcb_ = nullptr;
     callbacks_.OnConnectionRestarted();
   } else if (header.verification_tag == tcb_->my_verification_tag() &&
-             tcb_->peer_verification_tag() != cookie.initiate_tag()) {
+             tcb_->peer_verification_tag() != cookie.peer_tag()) {
     // TODO(boivie): Handle the peer_tag == 0?
     // "B) In this case, both sides may be attempting to start an
     // association at about the same time, but the peer endpoint started its
@@ -1404,7 +1406,7 @@
         << "Received COOKIE-ECHO indicating simultaneous connections";
     tcb_ = nullptr;
   } else if (header.verification_tag != tcb_->my_verification_tag() &&
-             tcb_->peer_verification_tag() == cookie.initiate_tag() &&
+             tcb_->peer_verification_tag() == cookie.peer_tag() &&
              cookie.tie_tag() == TieTag(0)) {
     // "C) In this case, the local endpoint's cookie has arrived late.
     // Before it arrived, the local endpoint sent an INIT and received an
@@ -1417,7 +1419,7 @@
         << "Received COOKIE-ECHO indicating a late COOKIE-ECHO. Discarding";
     return false;
   } else if (header.verification_tag == tcb_->my_verification_tag() &&
-             tcb_->peer_verification_tag() == cookie.initiate_tag()) {
+             tcb_->peer_verification_tag() == cookie.peer_tag()) {
     // "D) When both local and remote tags match, the endpoint should enter
     // the ESTABLISHED state, if it is in the COOKIE-ECHOED state.  It
     // should stop any cookie timer that may be running and send a COOKIE
diff --git a/net/dcsctp/socket/dcsctp_socket.h b/net/dcsctp/socket/dcsctp_socket.h
index deb6ee2..c59d6ca 100644
--- a/net/dcsctp/socket/dcsctp_socket.h
+++ b/net/dcsctp/socket/dcsctp_socket.h
@@ -148,8 +148,6 @@
 
   // Changes the socket state, given a `reason` (for debugging/logging).
   void SetState(State state, absl::string_view reason);
-  // Fills in `connect_params` with random verification tag and initial TSN.
-  void MakeConnectionParameters();
   // Closes the association. Note that the TCB will not be valid past this call.
   void InternalClose(ErrorKind error, absl::string_view message);
   // Closes the association, because of too many retransmission errors.
diff --git a/net/dcsctp/socket/dcsctp_socket_test.cc b/net/dcsctp/socket/dcsctp_socket_test.cc
index dc76b80..bb080d6 100644
--- a/net/dcsctp/socket/dcsctp_socket_test.cc
+++ b/net/dcsctp/socket/dcsctp_socket_test.cc
@@ -3061,5 +3061,149 @@
               testing::Optional(Property(&DcSctpMessage::ppid, PPID(53))));
 }
 
+TEST(DcSctpSocketTest, ResentInitHasSameParameters) {
+  // If an INIT chunk has to be resent (due to INIT_ACK not received in time),
+  // the resent INIT must have the same properties as the original one.
+  SocketUnderTest a("A");
+  SocketUnderTest z("Z");
+
+  a.socket.Connect();
+  auto packet_1 = a.cb.ConsumeSentPacket();
+
+  // Times out, INIT is re-sent.
+  AdvanceTime(a, z, a.options.t1_init_timeout.ToTimeDelta());
+  auto packet_2 = a.cb.ConsumeSentPacket();
+
+  ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet_1,
+                              SctpPacket::Parse(packet_1, z.options));
+  ASSERT_HAS_VALUE_AND_ASSIGN(
+      InitChunk init_chunk_1,
+      InitChunk::Parse(init_packet_1.descriptors()[0].data));
+
+  ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet_2,
+                              SctpPacket::Parse(packet_2, z.options));
+  ASSERT_HAS_VALUE_AND_ASSIGN(
+      InitChunk init_chunk_2,
+      InitChunk::Parse(init_packet_2.descriptors()[0].data));
+
+  EXPECT_EQ(init_chunk_1.initial_tsn(), init_chunk_2.initial_tsn());
+  EXPECT_EQ(init_chunk_1.initiate_tag(), init_chunk_2.initiate_tag());
+}
+
+TEST(DcSctpSocketTest, ResentInitAckHasDifferentParameters) {
+  // For every INIT, an INIT_ACK is produced. Verify that the socket doesn't
+  // maintain any state by ensuring that two created INIT_ACKs for the same
+  // received INIT are different.
+  SocketUnderTest a("A");
+  SocketUnderTest z("Z");
+
+  a.socket.Connect();
+  auto packet_1 = a.cb.ConsumeSentPacket();
+  EXPECT_THAT(packet_1, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+
+  z.socket.ReceivePacket(packet_1);
+  auto packet_2 = z.cb.ConsumeSentPacket();
+  z.socket.ReceivePacket(packet_1);
+  auto packet_3 = z.cb.ConsumeSentPacket();
+
+  EXPECT_THAT(packet_2,
+              HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+  EXPECT_THAT(packet_3,
+              HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+
+  ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_ack_packet_1,
+                              SctpPacket::Parse(packet_2, z.options));
+  ASSERT_HAS_VALUE_AND_ASSIGN(
+      InitAckChunk init_ack_chunk_1,
+      InitAckChunk::Parse(init_ack_packet_1.descriptors()[0].data));
+
+  ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_ack_packet_2,
+                              SctpPacket::Parse(packet_3, z.options));
+  ASSERT_HAS_VALUE_AND_ASSIGN(
+      InitAckChunk init_ack_chunk_2,
+      InitAckChunk::Parse(init_ack_packet_2.descriptors()[0].data));
+
+  EXPECT_NE(init_ack_chunk_1.initiate_tag(), init_ack_chunk_2.initiate_tag());
+  EXPECT_NE(init_ack_chunk_1.initial_tsn(), init_ack_chunk_2.initial_tsn());
+}
+
+TEST(DcSctpSocketResendInitTest, ConnectionCanContinueFromFirstInitAck) {
+  // If an INIT chunk has to be resent (due to INIT_ACK not received in time),
+  // another INIT will be sent, and if both INITs were actually received, both
+  // will be responded to by an INIT_ACK. While these two INIT_ACKs may have
+  // different parameters, the connection must be able to finish with the cookie
+  // (as replied to using COOKIE_ECHO) from either INIT_ACK.
+  SocketUnderTest a("A");
+  SocketUnderTest z("Z");
+
+  a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
+                              std::vector<uint8_t>(kLargeMessageSize)),
+                kSendOptions);
+  a.socket.Connect();
+  auto init_1 = a.cb.ConsumeSentPacket();
+
+  // Times out, INIT is re-sent.
+  AdvanceTime(a, z, a.options.t1_init_timeout.ToTimeDelta());
+  auto init_2 = a.cb.ConsumeSentPacket();
+
+  EXPECT_THAT(init_1, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+  EXPECT_THAT(init_2, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+
+  z.socket.ReceivePacket(init_1);
+  z.socket.ReceivePacket(init_2);
+  auto init_ack_1 = z.cb.ConsumeSentPacket();
+  auto init_ack_2 = z.cb.ConsumeSentPacket();
+  EXPECT_THAT(init_ack_1,
+              HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+  EXPECT_THAT(init_ack_2,
+              HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+
+  a.socket.ReceivePacket(init_ack_1);
+  // Then let the rest continue.
+  ExchangeMessages(a, z);
+
+  absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
+  ASSERT_TRUE(msg.has_value());
+  EXPECT_EQ(msg->stream_id(), StreamID(1));
+  EXPECT_THAT(msg->payload(), SizeIs(kLargeMessageSize));
+}
+
+TEST(DcSctpSocketResendInitTest, ConnectionCanContinueFromSecondInitAck) {
+  // Just as above, but discarding the first INIT_ACK.
+  SocketUnderTest a("A");
+  SocketUnderTest z("Z");
+
+  a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
+                              std::vector<uint8_t>(kLargeMessageSize)),
+                kSendOptions);
+  a.socket.Connect();
+  auto init_1 = a.cb.ConsumeSentPacket();
+
+  // Times out, INIT is re-sent.
+  AdvanceTime(a, z, a.options.t1_init_timeout.ToTimeDelta());
+  auto init_2 = a.cb.ConsumeSentPacket();
+
+  EXPECT_THAT(init_1, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+  EXPECT_THAT(init_2, HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
+
+  z.socket.ReceivePacket(init_1);
+  z.socket.ReceivePacket(init_2);
+  auto init_ack_1 = z.cb.ConsumeSentPacket();
+  auto init_ack_2 = z.cb.ConsumeSentPacket();
+  EXPECT_THAT(init_ack_1,
+              HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+  EXPECT_THAT(init_ack_2,
+              HasChunks(ElementsAre(IsChunkType(InitAckChunk::kType))));
+
+  a.socket.ReceivePacket(init_ack_2);
+  // Then let the rest continue.
+  ExchangeMessages(a, z);
+
+  absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
+  ASSERT_TRUE(msg.has_value());
+  EXPECT_EQ(msg->stream_id(), StreamID(1));
+  EXPECT_THAT(msg->payload(), SizeIs(kLargeMessageSize));
+}
+
 }  // namespace
 }  // namespace dcsctp
diff --git a/net/dcsctp/socket/state_cookie.cc b/net/dcsctp/socket/state_cookie.cc
index 624d783..c5ed1d8 100644
--- a/net/dcsctp/socket/state_cookie.cc
+++ b/net/dcsctp/socket/state_cookie.cc
@@ -32,17 +32,19 @@
   BoundedByteWriter<kCookieSize> buffer(cookie);
   buffer.Store32<0>(kMagic1);
   buffer.Store32<4>(kMagic2);
-  buffer.Store32<8>(*initiate_tag_);
-  buffer.Store32<12>(*initial_tsn_);
-  buffer.Store32<16>(a_rwnd_);
-  buffer.Store32<20>(static_cast<uint32_t>(*tie_tag_ >> 32));
-  buffer.Store32<24>(static_cast<uint32_t>(*tie_tag_));
-  buffer.Store8<28>(capabilities_.partial_reliability);
-  buffer.Store8<29>(capabilities_.message_interleaving);
-  buffer.Store8<30>(capabilities_.reconfig);
-  buffer.Store16<32>(capabilities_.negotiated_maximum_incoming_streams);
-  buffer.Store16<34>(capabilities_.negotiated_maximum_outgoing_streams);
-  buffer.Store8<36>(capabilities_.zero_checksum);
+  buffer.Store32<8>(*peer_tag_);
+  buffer.Store32<12>(*my_tag_);
+  buffer.Store32<16>(*peer_initial_tsn_);
+  buffer.Store32<20>(*my_initial_tsn_);
+  buffer.Store32<24>(a_rwnd_);
+  buffer.Store32<28>(static_cast<uint32_t>(*tie_tag_ >> 32));
+  buffer.Store32<32>(static_cast<uint32_t>(*tie_tag_));
+  buffer.Store8<36>(capabilities_.partial_reliability);
+  buffer.Store8<37>(capabilities_.message_interleaving);
+  buffer.Store8<38>(capabilities_.reconfig);
+  buffer.Store16<40>(capabilities_.negotiated_maximum_incoming_streams);
+  buffer.Store16<42>(capabilities_.negotiated_maximum_outgoing_streams);
+  buffer.Store8<44>(capabilities_.zero_checksum);
   return cookie;
 }
 
@@ -62,23 +64,25 @@
     return absl::nullopt;
   }
 
-  VerificationTag verification_tag(buffer.Load32<8>());
-  TSN initial_tsn(buffer.Load32<12>());
-  uint32_t a_rwnd = buffer.Load32<16>();
-  uint32_t tie_tag_upper = buffer.Load32<20>();
-  uint32_t tie_tag_lower = buffer.Load32<24>();
+  VerificationTag peer_tag(buffer.Load32<8>());
+  VerificationTag my_tag(buffer.Load32<12>());
+  TSN peer_initial_tsn(buffer.Load32<16>());
+  TSN my_initial_tsn(buffer.Load32<20>());
+  uint32_t a_rwnd = buffer.Load32<24>();
+  uint32_t tie_tag_upper = buffer.Load32<28>();
+  uint32_t tie_tag_lower = buffer.Load32<32>();
   TieTag tie_tag(static_cast<uint64_t>(tie_tag_upper) << 32 |
                  static_cast<uint64_t>(tie_tag_lower));
   Capabilities capabilities;
-  capabilities.partial_reliability = buffer.Load8<28>() != 0;
-  capabilities.message_interleaving = buffer.Load8<29>() != 0;
-  capabilities.reconfig = buffer.Load8<30>() != 0;
-  capabilities.negotiated_maximum_incoming_streams = buffer.Load16<32>();
-  capabilities.negotiated_maximum_outgoing_streams = buffer.Load16<34>();
-  capabilities.zero_checksum = buffer.Load8<36>() != 0;
+  capabilities.partial_reliability = buffer.Load8<36>() != 0;
+  capabilities.message_interleaving = buffer.Load8<37>() != 0;
+  capabilities.reconfig = buffer.Load8<38>() != 0;
+  capabilities.negotiated_maximum_incoming_streams = buffer.Load16<40>();
+  capabilities.negotiated_maximum_outgoing_streams = buffer.Load16<42>();
+  capabilities.zero_checksum = buffer.Load8<44>() != 0;
 
-  return StateCookie(verification_tag, initial_tsn, a_rwnd, tie_tag,
-                     capabilities);
+  return StateCookie(peer_tag, my_tag, peer_initial_tsn, my_initial_tsn, a_rwnd,
+                     tie_tag, capabilities);
 }
 
 }  // namespace dcsctp
diff --git a/net/dcsctp/socket/state_cookie.h b/net/dcsctp/socket/state_cookie.h
index 34cd6d3..b94eeda 100644
--- a/net/dcsctp/socket/state_cookie.h
+++ b/net/dcsctp/socket/state_cookie.h
@@ -27,15 +27,19 @@
 // Do not trust anything in it; no pointers or anything like that.
 class StateCookie {
  public:
-  static constexpr size_t kCookieSize = 37;
+  static constexpr size_t kCookieSize = 45;
 
-  StateCookie(VerificationTag initiate_tag,
-              TSN initial_tsn,
+  StateCookie(VerificationTag peer_tag,
+              VerificationTag my_tag,
+              TSN peer_initial_tsn,
+              TSN my_initial_tsn,
               uint32_t a_rwnd,
               TieTag tie_tag,
               Capabilities capabilities)
-      : initiate_tag_(initiate_tag),
-        initial_tsn_(initial_tsn),
+      : peer_tag_(peer_tag),
+        my_tag_(my_tag),
+        peer_initial_tsn_(peer_initial_tsn),
+        my_initial_tsn_(my_initial_tsn),
         a_rwnd_(a_rwnd),
         tie_tag_(tie_tag),
         capabilities_(capabilities) {}
@@ -47,15 +51,21 @@
   static absl::optional<StateCookie> Deserialize(
       rtc::ArrayView<const uint8_t> cookie);
 
-  VerificationTag initiate_tag() const { return initiate_tag_; }
-  TSN initial_tsn() const { return initial_tsn_; }
+  VerificationTag peer_tag() const { return peer_tag_; }
+  VerificationTag my_tag() const { return my_tag_; }
+  TSN peer_initial_tsn() const { return peer_initial_tsn_; }
+  TSN my_initial_tsn() const { return my_initial_tsn_; }
   uint32_t a_rwnd() const { return a_rwnd_; }
   TieTag tie_tag() const { return tie_tag_; }
   const Capabilities& capabilities() const { return capabilities_; }
 
  private:
-  const VerificationTag initiate_tag_;
-  const TSN initial_tsn_;
+  // Also called "Tag_A" in RFC4960.
+  const VerificationTag peer_tag_;
+  // Also called "Tag_Z" in RFC4960.
+  const VerificationTag my_tag_;
+  const TSN peer_initial_tsn_;
+  const TSN my_initial_tsn_;
   const uint32_t a_rwnd_;
   const TieTag tie_tag_;
   const Capabilities capabilities_;
diff --git a/net/dcsctp/socket/state_cookie_test.cc b/net/dcsctp/socket/state_cookie_test.cc
index 19be71a..806ea20 100644
--- a/net/dcsctp/socket/state_cookie_test.cc
+++ b/net/dcsctp/socket/state_cookie_test.cc
@@ -24,14 +24,18 @@
                                .zero_checksum = true,
                                .negotiated_maximum_incoming_streams = 123,
                                .negotiated_maximum_outgoing_streams = 234};
-  StateCookie cookie(VerificationTag(123), TSN(456),
+  StateCookie cookie(/*peer_tag=*/VerificationTag(123),
+                     /*my_tag=*/VerificationTag(321),
+                     /*peer_initial_tsn=*/TSN(456), /*my_initial_tsn=*/TSN(654),
                      /*a_rwnd=*/789, TieTag(101112), capabilities);
   std::vector<uint8_t> serialized = cookie.Serialize();
   EXPECT_THAT(serialized, SizeIs(StateCookie::kCookieSize));
   ASSERT_HAS_VALUE_AND_ASSIGN(StateCookie deserialized,
                               StateCookie::Deserialize(serialized));
-  EXPECT_EQ(deserialized.initiate_tag(), VerificationTag(123));
-  EXPECT_EQ(deserialized.initial_tsn(), TSN(456));
+  EXPECT_EQ(deserialized.peer_tag(), VerificationTag(123));
+  EXPECT_EQ(deserialized.my_tag(), VerificationTag(321));
+  EXPECT_EQ(deserialized.peer_initial_tsn(), TSN(456));
+  EXPECT_EQ(deserialized.my_initial_tsn(), TSN(654));
   EXPECT_EQ(deserialized.a_rwnd(), 789u);
   EXPECT_EQ(deserialized.tie_tag(), TieTag(101112));
   EXPECT_TRUE(deserialized.capabilities().partial_reliability);
@@ -48,7 +52,9 @@
   Capabilities capabilities = {.partial_reliability = true,
                                .message_interleaving = false,
                                .reconfig = true};
-  StateCookie cookie(VerificationTag(123), TSN(456),
+  StateCookie cookie(/*peer_tag=*/VerificationTag(123),
+                     /*my_tag=*/VerificationTag(321),
+                     /*peer_initial_tsn=*/TSN(456), /*my_initial_tsn=*/TSN(654),
                      /*a_rwnd=*/789, TieTag(101112), capabilities);
   std::vector<uint8_t> serialized = cookie.Serialize();
   ASSERT_THAT(serialized, SizeIs(StateCookie::kCookieSize));