xref: /aosp_15_r20/external/webrtc/net/dcsctp/socket/heartbeat_handler.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1*d9f75844SAndroid Build Coastguard Worker /*
2*d9f75844SAndroid Build Coastguard Worker  *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
3*d9f75844SAndroid Build Coastguard Worker  *
4*d9f75844SAndroid Build Coastguard Worker  *  Use of this source code is governed by a BSD-style license
5*d9f75844SAndroid Build Coastguard Worker  *  that can be found in the LICENSE file in the root of the source
6*d9f75844SAndroid Build Coastguard Worker  *  tree. An additional intellectual property rights grant can be found
7*d9f75844SAndroid Build Coastguard Worker  *  in the file PATENTS.  All contributing project authors may
8*d9f75844SAndroid Build Coastguard Worker  *  be found in the AUTHORS file in the root of the source tree.
9*d9f75844SAndroid Build Coastguard Worker  */
10*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/socket/heartbeat_handler.h"
11*d9f75844SAndroid Build Coastguard Worker 
12*d9f75844SAndroid Build Coastguard Worker #include <stddef.h>
13*d9f75844SAndroid Build Coastguard Worker 
14*d9f75844SAndroid Build Coastguard Worker #include <cstdint>
15*d9f75844SAndroid Build Coastguard Worker #include <memory>
16*d9f75844SAndroid Build Coastguard Worker #include <string>
17*d9f75844SAndroid Build Coastguard Worker #include <utility>
18*d9f75844SAndroid Build Coastguard Worker #include <vector>
19*d9f75844SAndroid Build Coastguard Worker 
20*d9f75844SAndroid Build Coastguard Worker #include "absl/functional/bind_front.h"
21*d9f75844SAndroid Build Coastguard Worker #include "absl/strings/string_view.h"
22*d9f75844SAndroid Build Coastguard Worker #include "absl/types/optional.h"
23*d9f75844SAndroid Build Coastguard Worker #include "api/array_view.h"
24*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/packet/bounded_byte_reader.h"
25*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/packet/bounded_byte_writer.h"
26*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
27*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
28*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/packet/parameter/heartbeat_info_parameter.h"
29*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/packet/parameter/parameter.h"
30*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/packet/sctp_packet.h"
31*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/public/dcsctp_options.h"
32*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/public/dcsctp_socket.h"
33*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/socket/context.h"
34*d9f75844SAndroid Build Coastguard Worker #include "net/dcsctp/timer/timer.h"
35*d9f75844SAndroid Build Coastguard Worker #include "rtc_base/logging.h"
36*d9f75844SAndroid Build Coastguard Worker 
37*d9f75844SAndroid Build Coastguard Worker namespace dcsctp {
38*d9f75844SAndroid Build Coastguard Worker 
39*d9f75844SAndroid Build Coastguard Worker // This is stored (in serialized form) as HeartbeatInfoParameter sent in
40*d9f75844SAndroid Build Coastguard Worker // HeartbeatRequestChunk and received back in HeartbeatAckChunk. It should be
41*d9f75844SAndroid Build Coastguard Worker // well understood that this data may be modified by the peer, so it can't
42*d9f75844SAndroid Build Coastguard Worker // be trusted.
43*d9f75844SAndroid Build Coastguard Worker //
44*d9f75844SAndroid Build Coastguard Worker // It currently only stores a timestamp, in millisecond precision, to allow for
45*d9f75844SAndroid Build Coastguard Worker // RTT measurements. If that would be manipulated by the peer, it would just
46*d9f75844SAndroid Build Coastguard Worker // result in incorrect RTT measurements, which isn't an issue.
47*d9f75844SAndroid Build Coastguard Worker class HeartbeatInfo {
48*d9f75844SAndroid Build Coastguard Worker  public:
49*d9f75844SAndroid Build Coastguard Worker   static constexpr size_t kBufferSize = sizeof(uint64_t);
50*d9f75844SAndroid Build Coastguard Worker   static_assert(kBufferSize == 8, "Unexpected buffer size");
51*d9f75844SAndroid Build Coastguard Worker 
HeartbeatInfo(TimeMs created_at)52*d9f75844SAndroid Build Coastguard Worker   explicit HeartbeatInfo(TimeMs created_at) : created_at_(created_at) {}
53*d9f75844SAndroid Build Coastguard Worker 
Serialize()54*d9f75844SAndroid Build Coastguard Worker   std::vector<uint8_t> Serialize() {
55*d9f75844SAndroid Build Coastguard Worker     uint32_t high_bits = static_cast<uint32_t>(*created_at_ >> 32);
56*d9f75844SAndroid Build Coastguard Worker     uint32_t low_bits = static_cast<uint32_t>(*created_at_);
57*d9f75844SAndroid Build Coastguard Worker 
58*d9f75844SAndroid Build Coastguard Worker     std::vector<uint8_t> data(kBufferSize);
59*d9f75844SAndroid Build Coastguard Worker     BoundedByteWriter<kBufferSize> writer(data);
60*d9f75844SAndroid Build Coastguard Worker     writer.Store32<0>(high_bits);
61*d9f75844SAndroid Build Coastguard Worker     writer.Store32<4>(low_bits);
62*d9f75844SAndroid Build Coastguard Worker     return data;
63*d9f75844SAndroid Build Coastguard Worker   }
64*d9f75844SAndroid Build Coastguard Worker 
Deserialize(rtc::ArrayView<const uint8_t> data)65*d9f75844SAndroid Build Coastguard Worker   static absl::optional<HeartbeatInfo> Deserialize(
66*d9f75844SAndroid Build Coastguard Worker       rtc::ArrayView<const uint8_t> data) {
67*d9f75844SAndroid Build Coastguard Worker     if (data.size() != kBufferSize) {
68*d9f75844SAndroid Build Coastguard Worker       RTC_LOG(LS_WARNING) << "Invalid heartbeat info: " << data.size()
69*d9f75844SAndroid Build Coastguard Worker                           << " bytes";
70*d9f75844SAndroid Build Coastguard Worker       return absl::nullopt;
71*d9f75844SAndroid Build Coastguard Worker     }
72*d9f75844SAndroid Build Coastguard Worker 
73*d9f75844SAndroid Build Coastguard Worker     BoundedByteReader<kBufferSize> reader(data);
74*d9f75844SAndroid Build Coastguard Worker     uint32_t high_bits = reader.Load32<0>();
75*d9f75844SAndroid Build Coastguard Worker     uint32_t low_bits = reader.Load32<4>();
76*d9f75844SAndroid Build Coastguard Worker 
77*d9f75844SAndroid Build Coastguard Worker     uint64_t created_at = static_cast<uint64_t>(high_bits) << 32 | low_bits;
78*d9f75844SAndroid Build Coastguard Worker     return HeartbeatInfo(TimeMs(created_at));
79*d9f75844SAndroid Build Coastguard Worker   }
80*d9f75844SAndroid Build Coastguard Worker 
created_at() const81*d9f75844SAndroid Build Coastguard Worker   TimeMs created_at() const { return created_at_; }
82*d9f75844SAndroid Build Coastguard Worker 
83*d9f75844SAndroid Build Coastguard Worker  private:
84*d9f75844SAndroid Build Coastguard Worker   const TimeMs created_at_;
85*d9f75844SAndroid Build Coastguard Worker };
86*d9f75844SAndroid Build Coastguard Worker 
HeartbeatHandler(absl::string_view log_prefix,const DcSctpOptions & options,Context * context,TimerManager * timer_manager)87*d9f75844SAndroid Build Coastguard Worker HeartbeatHandler::HeartbeatHandler(absl::string_view log_prefix,
88*d9f75844SAndroid Build Coastguard Worker                                    const DcSctpOptions& options,
89*d9f75844SAndroid Build Coastguard Worker                                    Context* context,
90*d9f75844SAndroid Build Coastguard Worker                                    TimerManager* timer_manager)
91*d9f75844SAndroid Build Coastguard Worker     : log_prefix_(std::string(log_prefix) + "heartbeat: "),
92*d9f75844SAndroid Build Coastguard Worker       ctx_(context),
93*d9f75844SAndroid Build Coastguard Worker       timer_manager_(timer_manager),
94*d9f75844SAndroid Build Coastguard Worker       interval_duration_(options.heartbeat_interval),
95*d9f75844SAndroid Build Coastguard Worker       interval_duration_should_include_rtt_(
96*d9f75844SAndroid Build Coastguard Worker           options.heartbeat_interval_include_rtt),
97*d9f75844SAndroid Build Coastguard Worker       interval_timer_(timer_manager_->CreateTimer(
98*d9f75844SAndroid Build Coastguard Worker           "heartbeat-interval",
99*d9f75844SAndroid Build Coastguard Worker           absl::bind_front(&HeartbeatHandler::OnIntervalTimerExpiry, this),
100*d9f75844SAndroid Build Coastguard Worker           TimerOptions(interval_duration_, TimerBackoffAlgorithm::kFixed))),
101*d9f75844SAndroid Build Coastguard Worker       timeout_timer_(timer_manager_->CreateTimer(
102*d9f75844SAndroid Build Coastguard Worker           "heartbeat-timeout",
103*d9f75844SAndroid Build Coastguard Worker           absl::bind_front(&HeartbeatHandler::OnTimeoutTimerExpiry, this),
104*d9f75844SAndroid Build Coastguard Worker           TimerOptions(options.rto_initial,
105*d9f75844SAndroid Build Coastguard Worker                        TimerBackoffAlgorithm::kExponential,
106*d9f75844SAndroid Build Coastguard Worker                        /*max_restarts=*/0))) {
107*d9f75844SAndroid Build Coastguard Worker   // The interval timer must always be running as long as the association is up.
108*d9f75844SAndroid Build Coastguard Worker   RestartTimer();
109*d9f75844SAndroid Build Coastguard Worker }
110*d9f75844SAndroid Build Coastguard Worker 
RestartTimer()111*d9f75844SAndroid Build Coastguard Worker void HeartbeatHandler::RestartTimer() {
112*d9f75844SAndroid Build Coastguard Worker   if (interval_duration_ == DurationMs(0)) {
113*d9f75844SAndroid Build Coastguard Worker     // Heartbeating has been disabled.
114*d9f75844SAndroid Build Coastguard Worker     return;
115*d9f75844SAndroid Build Coastguard Worker   }
116*d9f75844SAndroid Build Coastguard Worker 
117*d9f75844SAndroid Build Coastguard Worker   if (interval_duration_should_include_rtt_) {
118*d9f75844SAndroid Build Coastguard Worker     // The RTT should be used, but it's not easy accessible. The RTO will
119*d9f75844SAndroid Build Coastguard Worker     // suffice.
120*d9f75844SAndroid Build Coastguard Worker     interval_timer_->set_duration(interval_duration_ + ctx_->current_rto());
121*d9f75844SAndroid Build Coastguard Worker   } else {
122*d9f75844SAndroid Build Coastguard Worker     interval_timer_->set_duration(interval_duration_);
123*d9f75844SAndroid Build Coastguard Worker   }
124*d9f75844SAndroid Build Coastguard Worker 
125*d9f75844SAndroid Build Coastguard Worker   interval_timer_->Start();
126*d9f75844SAndroid Build Coastguard Worker }
127*d9f75844SAndroid Build Coastguard Worker 
HandleHeartbeatRequest(HeartbeatRequestChunk chunk)128*d9f75844SAndroid Build Coastguard Worker void HeartbeatHandler::HandleHeartbeatRequest(HeartbeatRequestChunk chunk) {
129*d9f75844SAndroid Build Coastguard Worker   // https://tools.ietf.org/html/rfc4960#section-8.3
130*d9f75844SAndroid Build Coastguard Worker   // "The receiver of the HEARTBEAT should immediately respond with a
131*d9f75844SAndroid Build Coastguard Worker   // HEARTBEAT ACK that contains the Heartbeat Information TLV, together with
132*d9f75844SAndroid Build Coastguard Worker   // any other received TLVs, copied unchanged from the received HEARTBEAT
133*d9f75844SAndroid Build Coastguard Worker   // chunk."
134*d9f75844SAndroid Build Coastguard Worker   ctx_->Send(ctx_->PacketBuilder().Add(
135*d9f75844SAndroid Build Coastguard Worker       HeartbeatAckChunk(std::move(chunk).extract_parameters())));
136*d9f75844SAndroid Build Coastguard Worker }
137*d9f75844SAndroid Build Coastguard Worker 
HandleHeartbeatAck(HeartbeatAckChunk chunk)138*d9f75844SAndroid Build Coastguard Worker void HeartbeatHandler::HandleHeartbeatAck(HeartbeatAckChunk chunk) {
139*d9f75844SAndroid Build Coastguard Worker   timeout_timer_->Stop();
140*d9f75844SAndroid Build Coastguard Worker   absl::optional<HeartbeatInfoParameter> info_param = chunk.info();
141*d9f75844SAndroid Build Coastguard Worker   if (!info_param.has_value()) {
142*d9f75844SAndroid Build Coastguard Worker     ctx_->callbacks().OnError(
143*d9f75844SAndroid Build Coastguard Worker         ErrorKind::kParseFailed,
144*d9f75844SAndroid Build Coastguard Worker         "Failed to parse HEARTBEAT-ACK; No Heartbeat Info parameter");
145*d9f75844SAndroid Build Coastguard Worker     return;
146*d9f75844SAndroid Build Coastguard Worker   }
147*d9f75844SAndroid Build Coastguard Worker   absl::optional<HeartbeatInfo> info =
148*d9f75844SAndroid Build Coastguard Worker       HeartbeatInfo::Deserialize(info_param->info());
149*d9f75844SAndroid Build Coastguard Worker   if (!info.has_value()) {
150*d9f75844SAndroid Build Coastguard Worker     ctx_->callbacks().OnError(ErrorKind::kParseFailed,
151*d9f75844SAndroid Build Coastguard Worker                               "Failed to parse HEARTBEAT-ACK; Failed to "
152*d9f75844SAndroid Build Coastguard Worker                               "deserialized Heartbeat info parameter");
153*d9f75844SAndroid Build Coastguard Worker     return;
154*d9f75844SAndroid Build Coastguard Worker   }
155*d9f75844SAndroid Build Coastguard Worker 
156*d9f75844SAndroid Build Coastguard Worker   TimeMs now = ctx_->callbacks().TimeMillis();
157*d9f75844SAndroid Build Coastguard Worker   if (info->created_at() > TimeMs(0) && info->created_at() <= now) {
158*d9f75844SAndroid Build Coastguard Worker     ctx_->ObserveRTT(now - info->created_at());
159*d9f75844SAndroid Build Coastguard Worker   }
160*d9f75844SAndroid Build Coastguard Worker 
161*d9f75844SAndroid Build Coastguard Worker   // https://tools.ietf.org/html/rfc4960#section-8.1
162*d9f75844SAndroid Build Coastguard Worker   // "The counter shall be reset each time ... a HEARTBEAT ACK is received from
163*d9f75844SAndroid Build Coastguard Worker   // the peer endpoint."
164*d9f75844SAndroid Build Coastguard Worker   ctx_->ClearTxErrorCounter();
165*d9f75844SAndroid Build Coastguard Worker }
166*d9f75844SAndroid Build Coastguard Worker 
OnIntervalTimerExpiry()167*d9f75844SAndroid Build Coastguard Worker absl::optional<DurationMs> HeartbeatHandler::OnIntervalTimerExpiry() {
168*d9f75844SAndroid Build Coastguard Worker   if (ctx_->is_connection_established()) {
169*d9f75844SAndroid Build Coastguard Worker     HeartbeatInfo info(ctx_->callbacks().TimeMillis());
170*d9f75844SAndroid Build Coastguard Worker     timeout_timer_->set_duration(ctx_->current_rto());
171*d9f75844SAndroid Build Coastguard Worker     timeout_timer_->Start();
172*d9f75844SAndroid Build Coastguard Worker     RTC_DLOG(LS_INFO) << log_prefix_ << "Sending HEARTBEAT with timeout "
173*d9f75844SAndroid Build Coastguard Worker                       << *timeout_timer_->duration();
174*d9f75844SAndroid Build Coastguard Worker 
175*d9f75844SAndroid Build Coastguard Worker     Parameters parameters = Parameters::Builder()
176*d9f75844SAndroid Build Coastguard Worker                                 .Add(HeartbeatInfoParameter(info.Serialize()))
177*d9f75844SAndroid Build Coastguard Worker                                 .Build();
178*d9f75844SAndroid Build Coastguard Worker 
179*d9f75844SAndroid Build Coastguard Worker     ctx_->Send(ctx_->PacketBuilder().Add(
180*d9f75844SAndroid Build Coastguard Worker         HeartbeatRequestChunk(std::move(parameters))));
181*d9f75844SAndroid Build Coastguard Worker   } else {
182*d9f75844SAndroid Build Coastguard Worker     RTC_DLOG(LS_VERBOSE)
183*d9f75844SAndroid Build Coastguard Worker         << log_prefix_
184*d9f75844SAndroid Build Coastguard Worker         << "Will not send HEARTBEAT when connection not established";
185*d9f75844SAndroid Build Coastguard Worker   }
186*d9f75844SAndroid Build Coastguard Worker   return absl::nullopt;
187*d9f75844SAndroid Build Coastguard Worker }
188*d9f75844SAndroid Build Coastguard Worker 
OnTimeoutTimerExpiry()189*d9f75844SAndroid Build Coastguard Worker absl::optional<DurationMs> HeartbeatHandler::OnTimeoutTimerExpiry() {
190*d9f75844SAndroid Build Coastguard Worker   // Note that the timeout timer is not restarted. It will be started again when
191*d9f75844SAndroid Build Coastguard Worker   // the interval timer expires.
192*d9f75844SAndroid Build Coastguard Worker   RTC_DCHECK(!timeout_timer_->is_running());
193*d9f75844SAndroid Build Coastguard Worker   ctx_->IncrementTxErrorCounter("HEARTBEAT timeout");
194*d9f75844SAndroid Build Coastguard Worker   return absl::nullopt;
195*d9f75844SAndroid Build Coastguard Worker }
196*d9f75844SAndroid Build Coastguard Worker }  // namespace dcsctp
197