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